diff --git a/.clang-tidy b/.clang-tidy index 26f2b38730..5af5cd29f0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,49 +1,56 @@ --- -# All Clang-Tidy Checks allowed, except: -# - forbidden vararg -# - forbidden magic numbers -# - forbidden namespace "using" -# - forbidden array->pointer decay -# - init of static memory may cause an exception (cert-err58) -# - forbidden implicit conversion from pointer/int to bool -# - recommended auto -# - remove llvm-specific checks (header guard style, usage of llvm namespace, restriction of libc includes, etc.) -# Naming conventions set to snake_case -Checks: '*,-fuchsia-*, - -cppcoreguidelines-pro-type-vararg,-hicpp-vararg, - -cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers, - -cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay, - -cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-pro-type-cstyle-cast, - -cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-static-cast-downcast, - -cppcoreguidelines-macro-usage, - -cppcoreguidelines-avoid-const-or-ref-data-members, - -cppcoreguidelines-non-private-member-variables-in-classes, - -cppcoreguidelines-special-member-functions, - -cppcoreguidelines-avoid-do-while, - -modernize-use-using,-modernize-use-trailing-return-type, - -modernize-use-auto,-hicpp-use-auto, - -llvmlibc-callee-namespace,-llvmlibc-implementation-in-namespace,-llvmlibc-restrict-system-libc-headers, - -llvm-header-guard,-llvmlibc-inline-function-decl, - -bugprone-easily-swappable-parameters, - -google-runtime-references,-google-readability-casting,-google-build-using-namespace, - -google-readability-avoid-underscore-in-googletest-name, - google-default-arguments,-cppcoreguidelines-pro-bounds-pointer-arithmetic, - -cert-err58-cpp, - -altera-unroll-loops,-altera-id-dependent-backward-branch, - -readability-function-cognitive-complexity,-readability-isolate-declaration, - -misc-non-private-member-variables-in-classes,-altera-struct-pack-align,-readability-uppercase-literal-suffix, +Checks: '-*, + bugprone-copy-constructor-init, + bugprone-forward-declaration-namespace, + bugprone-inaccurate-erase, + bugprone-move-forwarding-reference, + bugprone-parent-virtual-call, + bugprone-reserved-identifier, + bugprone-suspicious-memset-usage, + bugprone-suspicious-semicolon, + bugprone-undefined-memory-manipulation, + bugprone-use-after-move, + cppcoreguidelines-macro-usage, + cppcoreguidelines-missing-std-forward, + cppcoreguidelines-pro-type-cstyle-cast, + cppcoreguidelines-slicing, + cppcoreguidelines-virtual-class-destructor, + llvm-namespace-comment, + misc-*, + -misc-const-correctness, + -misc-no-recursion, + -misc-unused-parameters, -misc-use-anonymous-namespace, - -hicpp-special-member-functions, + -misc-non-private-member-variables-in-classes, + modernize-*, + -modernize-avoid-c-arrays, + -modernize-use-trailing-return-type, + performance-*, + readability-*, + -readability-avoid-unconditional-preprocessor-if, + -readability-function-cognitive-complexity, + -readability-function-size, -readability-identifier-length, - readability-identifier-naming' -HeaderFilterRegex: '' -AnalyzeTemporaryDtors: false + -readability-implicit-bool-conversion, + -readability-magic-numbers, + -readability-static-accessed-through-instance, + -readability-uppercase-literal-suffix' CheckOptions: - - key: readability-identifier-naming.NamespaceCase - value: lower_case - key: readability-identifier-naming.ClassCase value: lower_case + - key: readability-identifier-naming.EnumCase + value: lower_case + - key: readability-identifier-naming.FunctionCase + value: lower_case + - key: readability-identifier-naming.MemberCase + value: lower_case + - key: readability-identifier-naming.NamespaceCase + value: lower_case - key: readability-identifier-naming.StructCase value: lower_case - key: readability-identifier-naming.VariableCase value: lower_case + - key: readability-identifier-naming.StaticConstantCase + value: aNy_CasE + - key: readability-identifier-naming.GlobalConstantCase + value: aNy_CasE diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6eb427bbe1..edcb351398 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,19 +25,19 @@ include: - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/setup/all.yml - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/features/all.yml - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/tools/python.yml - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/tools/test_reporter.yml - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/tools/tagger.yml - local: .gitlab/ci/builders/version.yml - local: .gitlab/ci/build.yml @@ -140,7 +140,7 @@ full-code-format: - static-analyzer.sh -i /tmp/codechecker_skip --analyzers ${ANALYZER} ${ANALYZER_ARGS} $CI_PROJECT_DIR after_script: - mv codechecker_html codechecker-${ANALYZER}-html - artifacts: + artifacts: &codechecker_artifacts reports: codequality: code-quality-report.json paths: @@ -159,7 +159,7 @@ clang-tidy: allow_failure: false variables: ANALYZER: clang-tidy - ANALYZER_ARGS: --tidy-config .clang-tidy + ANALYZER_ARGS: --analyzer-config clang-tidy:take-config-from-directory=true --tidy-config .clang-tidy ARTIFACT_EXTRA_PATH: "/index.html" cppcheck: @@ -170,6 +170,9 @@ cppcheck: variables: ANALYZER: cppcheck ANALYZER_ARGS: --cppcheck-max-template-recursion 10 + artifacts: + <<: *codechecker_artifacts + expire_in: 1 day clangsa: extends: .codechecker @@ -203,12 +206,12 @@ clangsa: cov-build --dir cov-int make -j${KUBERNETES_CPU_REQUEST} tar czvf srsgnb.tgz cov-int ver=$(git rev-parse HEAD) - + - | curl --form token=$COV_TOKEN \ - --form email=nils@srs.io \ + --form email=${COVERITY_EMAIL} \ --form file=@srsgnb.tgz \ --form version=$ver \ - --form description="srsRAN Project dev build" \ + --form description="${DESCRIPTION}" \ https://scan.coverity.com/builds?project=${PROJECT_NAME} coverity-dev: @@ -280,11 +283,6 @@ unit coverage: coverage_report: summary when: always # Even if previous stages/required jobs fail allow_failure: true - - if: $CI_DESCRIPTION =~ /Nightly/ - variables: - coverage_report: full - when: always # Even if previous stages/required jobs fail - allow_failure: true before_script: - PACKAGE_URL=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/coverage/${CI_COMMIT_BRANCH}${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}/coverage_history.tar.gz # Download coverage history from the registry @@ -329,10 +327,11 @@ unit coverage: - pip3 install --verbose retina-reporter --index-url https://__token__:$CODEBOT_TOKEN@gitlab.com/api/v4/projects/44296988/packages/pypi/simple - retina-reporter --input build_time_metrics.json --bucket ci --db-config token=$INFLUXDB_TOKEN org=$INFLUXDB_ORG url=$INFLUXDB_URL coverage: /^\s*Line coverage:\s*\d+.\d+\%/ - artifacts: + artifacts: &unit_coverage_artifacts paths: - coverage_html expire_in: 10 minutes + retry: 2 needs: - job: smoke release cached optional: true @@ -344,29 +343,41 @@ unit coverage: optional: true artifacts: true +unit coverage dev: + extends: unit coverage + rules: + - if: $CI_DESCRIPTION =~ /Nightly/ + variables: + coverage_report: full + when: always # Even if previous stages/required jobs fail + allow_failure: true + artifacts: + <<: *unit_coverage_artifacts + expire_in: 1 day + pages: stage: documentation rules: - if: $CI_DESCRIPTION == "Nightly" when: always # Even if previous stages/required jobs fail allow_failure: true - image: ${GITLAB_REGISTRY_URI}/${CI_TOOLS_REPO}/doxygen:2.0.0 + image: ${GITLAB_REGISTRY_URI}/${CI_TOOLS_REPO}/doxygen:1.9.8-1.2023.7 script: - mkdir public - mv coverage_html public/ - mv codechecker*html public/ - mv docs/index.html public/index.html - .gitlab/ci/builders/install_dependencies.sh + - apt-get update && apt-get install -y --no-install-recommends rsync - | rm -Rf build mkdir build cd build || exit - cmake -G Ninja .. - ninja doxygen - mv ./docs/html ./public + cmake .. + make -j $(nproc) doxygen cd .. - - mv build/public/index.html build/public/index_doxygen.html - - find build/public/ -name '*.*' -exec mv {} public/ \; + - mkdir public/doxygen + - rsync -a build/docs/html/ public/doxygen/ after_script: - | if [ $CI_JOB_STATUS = "failed" ]; then @@ -375,32 +386,11 @@ pages: fi - mv docs/*.png public/ - sed -i 's/commit_hash/'$CI_COMMIT_SHA'/' public/index.html - - sed -i 's/zmq_job_id/'$( cat ./zmq/job.env )'/' public/index.html - - sed -i 's/zmq_asan_job_id/'$( cat ./zmq-asan/job.env )'/' public/index.html - - sed -i 's/zmq_valgrind_job_id/'$( cat ./zmq-valgrind/job.env )'/' public/index.html - - sed -i 's/rf_job_id/'$( cat ./rf/job.env )'/' public/index.html - - sed -i 's/rf_tsan_job_id/'$( cat ./rf-tsan/job.env )'/' public/index.html - - sed -i 's/rf_asan_job_id/'$( cat ./rf-asan/job.env )'/' public/index.html - - sed -i 's/rf_b200_config_job_id/'$( cat ./rf-b200-config/job.env )'/' public/index.html needs: - - job: unit coverage + - job: unit coverage dev artifacts: true - job: cppcheck artifacts: true - - job: zmq - artifacts: true - - job: zmq-asan - artifacts: true - - job: zmq-valgrind - artifacts: true - - job: rf - artifacts: true - - job: rf-tsan - artifacts: true - - job: rf-asan - artifacts: true - - job: rf-b200-config - artifacts: true artifacts: paths: - public diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index 258008218c..d166df8d2b 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -20,7 +20,7 @@ include: - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/setup/all.yml - project: softwareradiosystems/ci/srsran_project_packaging ref: "1" @@ -293,7 +293,7 @@ variables: rm -Rf build_time_metrics.json fi timeout: 4h - artifacts: + artifacts: &build_artifacts when: always reports: coverage_report: @@ -552,6 +552,9 @@ smoke release update cache: cache: - !reference [.fetch_src_cache, cache] - *cache_build_set + artifacts: + <<: *build_artifacts + expire_in: 1 day smoke tsan update cache: extends: .smoke tsan @@ -566,6 +569,9 @@ smoke tsan update cache: cache: - !reference [.fetch_src_cache, cache] - *cache_build_set + artifacts: + <<: *build_artifacts + expire_in: 1 day smoke rhel update cache: extends: .smoke rhel @@ -607,6 +613,9 @@ basic rel with deb info: ENABLE_AVX512: "False" SAVE_ARTIFACTS: "True" tags: ["${AMD64_AVX2_TAG}"] + artifacts: + <<: *build_artifacts + expire_in: 1 day basic asan: extends: .build_and_unit @@ -625,6 +634,9 @@ basic asan: ENABLE_AVX512: "False" SAVE_ARTIFACTS: "True" tags: ["${AMD64_AVX2_TAG}"] + artifacts: + <<: *build_artifacts + expire_in: 1 day basic valgrind: extends: .build_and_unit @@ -642,6 +654,9 @@ basic valgrind: TEST_MODE: valgrind SAVE_ARTIFACTS: "True" tags: ["${AMD64_TAG}"] + artifacts: + <<: *build_artifacts + expire_in: 1 day # Packaging diff --git a/.gitlab/ci/builders.yml b/.gitlab/ci/builders.yml index 7ab034af86..605b4151e7 100644 --- a/.gitlab/ci/builders.yml +++ b/.gitlab/ci/builders.yml @@ -1,15 +1,15 @@ include: - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/setup/default.yml - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/setup/workflow.yml - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/tools/docker.yml - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/setup/versions.yml - local: .gitlab/ci/builders/version.yml - local: .gitlab/ci/src_cache.yml diff --git a/.gitlab/ci/e2e.yml b/.gitlab/ci/e2e.yml index 3e05f91a0d..bea8bde808 100644 --- a/.gitlab/ci/e2e.yml +++ b/.gitlab/ci/e2e.yml @@ -20,7 +20,7 @@ include: - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/setup/all.yml variables: @@ -61,6 +61,10 @@ variables: .txrx-lib: &txrx-lib - job: "build trx driver" + optional: true + artifacts: true + - job: "build amariue zmq driver" + optional: true artifacts: true load retina variables: @@ -79,7 +83,7 @@ load retina variables: .prepare_test: variables: - KUBECONFIG_VAR_NAME: "RETINA_HIGH_PERFORMANCE_LAB_KUBECONFIG" + KUBECONFIG_VAR_NAME: "RETINA_NAMESPACE_KUBECONFIG" before_script: - | eval K_PATH="\$$KUBECONFIG_VAR_NAME" @@ -99,7 +103,7 @@ load retina variables: variables: ARTIFACT_COMPRESSION_LEVEL: "slowest" KUBERNETES_EPHEMERAL_STORAGE_REQUEST: "5G" - KUBECONFIG_VAR_NAME: "RETINA_HIGH_PERFORMANCE_LAB_KUBECONFIG" + KUBECONFIG_VAR_NAME: "RETINA_NAMESPACE_KUBECONFIG" GROUP: zmq tags: - on-prem-amd64 @@ -119,7 +123,7 @@ load retina variables: - | cd tests/e2e export LOGNAME=ci_${GROUP}_${GITLAB_USER_LOGIN} - - retina-launcher --retina-request="${CI_PROJECT_DIR}/.gitlab/ci/e2e/retina_request_${TESTBED}.yml" --log-folder=./log --html=./log/report.html --self-contained-html --junitxml=out.xml ${PYTEST_ARGS} -k "${KEYWORDS}" -m "${MARKERS}" --register-parameter ue.all.log_level=$E2E_LOG_LEVEL gnb.all.log_level=$E2E_LOG_LEVEL ${RETINA_ARGS} + - retina-launcher --retina-request="${CI_PROJECT_DIR}/.gitlab/ci/e2e/retina_request_${TESTBED}.yml" --log-folder=./log --html=./log/report.html --self-contained-html --junitxml=out.xml ${PYTEST_ARGS} --reruns 3 --only-rerun ErrorReportedByAgent --only-rerun ValidationError -k "${KEYWORDS}" -m "${MARKERS}" --register-parameter ue.all.log_level=$E2E_LOG_LEVEL gnb.all.log_level=$E2E_LOG_LEVEL ${RETINA_ARGS} after_script: - | echo "*******************************************************************************************************************************" @@ -277,10 +281,17 @@ rf: KEYWORDS: "not iperf" E2E_LOG_LEVEL: "info" -rf-iperf: +rf-iperf-udp: extends: .rf variables: - KEYWORDS: "iperf" + KEYWORDS: "iperf and udp" + E2E_LOG_LEVEL: "info" + +rf-iperf-tcp: + extends: .rf + variables: + KEYWORDS: "iperf and tcp" + E2E_LOG_LEVEL: "info" rf-tsan: extends: .rf @@ -324,29 +335,32 @@ android: TESTBED: "android_b200" MARKERS: "android" E2E_LOG_LEVEL: "warning" - KUBECONFIG_VAR_NAME: "RETINA_HIGH_PERFORMANCE_LAB_KUBECONFIG" + KUBECONFIG_VAR_NAME: "RETINA_NAMESPACE_KUBECONFIG" needs: - job: "basic rel with deb info" artifacts: true - *retina-needs -android-hp: +android-x300: extends: .e2e-run rules: - if: $CI_DESCRIPTION =~ /Nightly/ - allow_failure: true variables: GROUP: "rf" TESTBED: "android_x300" MARKERS: "android_hp" E2E_LOG_LEVEL: "info" - KUBECONFIG_VAR_NAME: "RETINA_HIGH_PERFORMANCE_LAB_KUBECONFIG" - PYTEST_ARGS: --graph-url $INFLUXDB_URL@$INFLUXDB_ORG:$INFLUXDB_TOKEN --graph-bucket e2e + KUBECONFIG_VAR_NAME: "RETINA_NAMESPACE_KUBECONFIG" needs: - job: "basic rel with deb info" artifacts: true - *retina-needs +android-n300: + extends: android-x300 + variables: + TESTBED: "android_n300" + ################################################################################ # Garbage collector ################################################################################ diff --git a/.gitlab/ci/e2e/.env b/.gitlab/ci/e2e/.env index fc4e85ead5..cfcf3eaf7e 100644 --- a/.gitlab/ci/e2e/.env +++ b/.gitlab/ci/e2e/.env @@ -1,5 +1,5 @@ RETINA_REGISTRY_PREFIX=registry.gitlab.com/softwareradiosystems/ci/retina -RETINA_VERSION=0.20.1 +RETINA_VERSION=0.30.5 AMARISOFT_VERSION=2023-03-17 SRSUE_VERSION=23.04.01 OPEN5GS_VERSION=2.5.6 diff --git a/.gitlab/ci/e2e/retina_request_android_n300.yml b/.gitlab/ci/e2e/retina_request_android_n300.yml index 6eb29fd077..c14c247699 100644 --- a/.gitlab/ci/e2e/retina_request_android_n300.yml +++ b/.gitlab/ci/e2e/retina_request_android_n300.yml @@ -40,10 +40,10 @@ requests: "3G" limit: "3G" mode: grpc + taints: ["retina"] resources: - type: sdr model: n3xx - nof_ant: 2 shared_files: - local_path: ../../build/apps/gnb/gnb remote_path: /usr/local/bin diff --git a/.gitlab/ci/e2e/retina_request_android_x300.yml b/.gitlab/ci/e2e/retina_request_android_x300.yml index e9ac64cee2..ef418a77b8 100644 --- a/.gitlab/ci/e2e/retina_request_android_x300.yml +++ b/.gitlab/ci/e2e/retina_request_android_x300.yml @@ -12,8 +12,8 @@ requirements: arch: amd64 cpu: - requests: 4 - limits: 4 + requests: 1 + limits: 1 memory: requests: "2G" limits: "2G" @@ -31,19 +31,19 @@ requirements: arch: amd64 cpu: - requests: 10 - limits: 10 + requests: 11 + limits: 11 memory: - requests: "20G" - limits: "20G" + requests: "12G" + limits: "12G" ephemeral-storage: requests: "3G" limit: "3G" mode: grpc + taints: ["retina"] resources: - type: sdr model: x300 - nof_ant: 2 shared_files: - local_path: ../../build/apps/gnb/gnb remote_path: /usr/local/bin diff --git a/.gitlab/ci/e2e/retina_request_zmq_4x4_mimo.yml b/.gitlab/ci/e2e/retina_request_zmq_4x4_mimo.yml index d7eee3022f..aac8509dd0 100644 --- a/.gitlab/ci/e2e/retina_request_zmq_4x4_mimo.yml +++ b/.gitlab/ci/e2e/retina_request_zmq_4x4_mimo.yml @@ -15,7 +15,6 @@ requests: 1 memory: requests: "8G" - limits: "8G" ephemeral-storage: requests: "3G" limit: "3G" @@ -38,8 +37,7 @@ cpu: requests: 1 memory: - requests: "3G" - limits: "3G" + requests: "8G" ephemeral-storage: requests: "3G" limit: "3G" @@ -60,7 +58,6 @@ requests: 1 memory: requests: "4G" - limits: "4G" ephemeral-storage: requests: "3G" limit: "3G" diff --git a/.gitlab/ci/e2e/retina_request_zmq_srsue.yml b/.gitlab/ci/e2e/retina_request_zmq_srsue.yml index f4d4f887d9..a680661e21 100644 --- a/.gitlab/ci/e2e/retina_request_zmq_srsue.yml +++ b/.gitlab/ci/e2e/retina_request_zmq_srsue.yml @@ -15,7 +15,6 @@ requests: 1 memory: requests: "8G" - limits: "8G" ephemeral-storage: requests: "3G" limit: "3G" @@ -30,7 +29,6 @@ requests: 1 memory: requests: "8G" - limits: "8G" ephemeral-storage: requests: "3G" limit: "3G" @@ -50,7 +48,6 @@ requests: 1 memory: requests: "4G" - limits: "4G" ephemeral-storage: requests: "3G" limit: "3G" diff --git a/.gitlab/ci/release.yml b/.gitlab/ci/release.yml index a261ce5998..d96aca6bb1 100644 --- a/.gitlab/ci/release.yml +++ b/.gitlab/ci/release.yml @@ -8,10 +8,10 @@ include: - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/setup/all.yml - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/features/all.yml stages: @@ -62,10 +62,18 @@ coverity-agpl: stage: private rules: - if: $ON_TAG + variables: + GIT_STRATEGY: none + PRIVATE_BRANCH: agpl_main before_script: - export PROJECT_NAME="srsRAN_5G_agpl" - export DESCRIPTION="srsRAN Project AGPL build" - export COV_TOKEN="${COVERITY_TOKEN_AGPL}" + # Download agpl branch + - git config --global user.name "${CODEBOT_USERNAME}" + - git config --global user.email "${CODEBOT_LONG_USERNAME}@noreply.gitlab.com" + - git clone --depth 1 --branch $PRIVATE_BRANCH https://${CODEBOT_USERNAME}:${CODEBOT_TOKEN}@gitlab.com/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}.git /${CI_PROJECT_NAME} + - cd /${CI_PROJECT_NAME} needs: - job: update agpl main optional: false diff --git a/.gitlab/ci/trx.yml b/.gitlab/ci/trx.yml index a0eb914269..d2b77dc106 100644 --- a/.gitlab/ci/trx.yml +++ b/.gitlab/ci/trx.yml @@ -8,7 +8,7 @@ include: - project: softwareradiosystems/ci/tools - ref: "12" + ref: "13" file: .gitlab/ci-shared/setup/all.yml - local: .gitlab/ci/build.yml @@ -24,7 +24,6 @@ build trx driver: timeout: 30 min rules: - if: $ON_MR - - if: $CI_DESCRIPTION =~ /Nightly/ - if: $ON_WEB retry: 2 script: @@ -73,7 +72,15 @@ build trx driver: mv ${CI_PROJECT_DIR}/build/utils/trx_srsran/libtrx_srsran.so ${CI_PROJECT_DIR}/build_trx_srsran/ after_script: [] - artifacts: + artifacts: &trx_artifacts paths: - build_trx_srsran/libtrx_srsran.so expire_in: 10 minutes + +build amariue zmq driver: + extends: build trx driver + rules: + - if: $CI_DESCRIPTION =~ /Nightly/ + artifacts: + <<: *trx_artifacts + expire_in: 1 day diff --git a/COPYRIGHT b/COPYRIGHT index 3fa1fd41fc..88619c81c4 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -50,6 +50,16 @@ Copyright: 2015-2017 Michael Park License: Boost Software License, Version 1.0 +Files: external/rigtorp/MPMCQueue.hpp +Copyright: 2018 Erik Rigtorp +License: MIT + + +Files: external/rigtorp/SPSCQueue.hpp +Copyright: 2018 Erik Rigtorp +License: MIT + + License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/README.md b/README.md index 9c8f25cfc1..c16e86cc03 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ srsRAN Project ============== [![Build Status](https://github.com/srsran/srsRAN_Project/actions/workflows/ccpp.yml/badge.svg?branch=main)](https://github.com/srsran/srsRAN_Project/actions/workflows/ccpp.yml) -[![CodeQL](https://github.com/srsran/srsRAN_Project/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/srsran/srsRAN_Project/actions/workflows/codeql.yml) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7868/badge)](https://www.bestpractices.dev/projects/7868) The srsRAN Project is a complete 5G RAN solution, featuring an ORAN-native CU/DU developed by [SRS](http://www.srs.io). @@ -34,33 +34,33 @@ You can install the build tools and mandatory requirements for some example dist
Ubuntu 22.04 - ```bash sudo apt-get install cmake make gcc g++ pkg-config libfftw3-dev libmbedtls-dev libsctp-dev libyaml-cpp-dev libgtest-dev ``` +
Fedora - ```bash sudo yum install cmake make gcc gcc-c++ fftw-devel lksctp-tools-devel yaml-cpp-devel mbedtls-devel gtest-devel ``` +
Arch Linux - ```bash sudo pacman -S cmake make base-devel fftw mbedtls yaml-cpp lksctp-tools gtest ``` +
The srsRAN Project supports split-8 and split-7.2 fronthaul. Split-8 fronthaul is supported via UHD for USRP devices: * UHD: * See UHD documentation for installation instructions. - + Build Instructions ------------------ diff --git a/apps/examples/du/phy_factory.cpp b/apps/examples/du/phy_factory.cpp index f7f72ecd5f..0ff0eea74b 100644 --- a/apps/examples/du/phy_factory.cpp +++ b/apps/examples/du/phy_factory.cpp @@ -74,6 +74,8 @@ std::unique_ptr srsran::create_upper_phy(const upper_phy_params& upper_config.nof_slots_ul_rg = ul_pipeline_depth * nof_slots_per_subframe; upper_config.nof_ul_processors = upper_config.nof_slots_ul_rg; upper_config.max_ul_thread_concurrency = 4; + upper_config.max_pusch_concurrency = 1; + upper_config.nof_pusch_decoder_threads = 1; upper_config.nof_prach_buffer = prach_pipeline_depth * nof_slots_per_subframe; upper_config.max_nof_td_prach_occasions = 1; upper_config.max_nof_fd_prach_occasions = 1; @@ -96,6 +98,7 @@ std::unique_ptr srsran::create_upper_phy(const upper_phy_params& upper_config.rg_gateway = rg_gateway; upper_config.pucch_executor = ul_executor; upper_config.pusch_executor = ul_executor; + upper_config.pusch_decoder_executor = nullptr; upper_config.prach_executor = ul_executor; upper_config.rx_symbol_request_notifier = rx_symbol_request_notifier; upper_config.crc_calculator_type = "auto"; diff --git a/apps/gnb/CMakeLists.txt b/apps/gnb/CMakeLists.txt index 88b95538b6..e0ebd56cd7 100644 --- a/apps/gnb/CMakeLists.txt +++ b/apps/gnb/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable(gnb gnb_e2_metric_connector_manager.cpp gnb_du_factory.cpp helpers/metrics_plotter_stdout.cpp + helpers/metrics_plotter_json.cpp helpers/gnb_console_helper.cpp helpers/metrics_hub.cpp adapters/e1ap_gateway_local_connector.cpp diff --git a/apps/gnb/gnb.cpp b/apps/gnb/gnb.cpp index b2c8007200..955b1cb476 100644 --- a/apps/gnb/gnb.cpp +++ b/apps/gnb/gnb.cpp @@ -313,7 +313,9 @@ int main(int argc, char** argv) e2ap_logger.set_hex_dump_max_size(gnb_cfg.log_cfg.hex_max_size); if (not gnb_cfg.log_cfg.tracing_filename.empty()) { + gnb_logger.info("Opening event tracer..."); open_trace_file(gnb_cfg.log_cfg.tracing_filename); + gnb_logger.info("Event tracer opened successfully"); } // Log build info @@ -336,30 +338,38 @@ int main(int argc, char** argv) check_drm_kms_polling(gnb_logger); // Set layer-specific pcap options. - std::unique_ptr ngap_p = std::make_unique(PCAP_NGAP_DLT, "NGAP"); + const auto& low_prio_cpu_mask = gnb_cfg.expert_execution_cfg.affinities.low_priority_cpu_cfg.mask; + + std::unique_ptr ngap_p = std::make_unique(PCAP_NGAP_DLT, "NGAP", low_prio_cpu_mask); if (gnb_cfg.pcap_cfg.ngap.enabled) { - ngap_p->open(gnb_cfg.pcap_cfg.ngap.filename.c_str()); + ngap_p->open(gnb_cfg.pcap_cfg.ngap.filename); } - std::unique_ptr e1ap_p = std::make_unique(PCAP_E1AP_DLT, "E1AP"); + std::unique_ptr e1ap_p = std::make_unique(PCAP_E1AP_DLT, "E1AP", low_prio_cpu_mask); if (gnb_cfg.pcap_cfg.e1ap.enabled) { - e1ap_p->open(gnb_cfg.pcap_cfg.e1ap.filename.c_str()); + e1ap_p->open(gnb_cfg.pcap_cfg.e1ap.filename); } - std::unique_ptr f1ap_p = std::make_unique(PCAP_F1AP_DLT, "F1AP"); + std::unique_ptr f1ap_p = std::make_unique(PCAP_F1AP_DLT, "F1AP", low_prio_cpu_mask); if (gnb_cfg.pcap_cfg.f1ap.enabled) { - f1ap_p->open(gnb_cfg.pcap_cfg.f1ap.filename.c_str()); + f1ap_p->open(gnb_cfg.pcap_cfg.f1ap.filename); } - std::unique_ptr e2ap_p = std::make_unique(PCAP_E2AP_DLT, "E2AP"); + std::unique_ptr e2ap_p = std::make_unique(PCAP_E2AP_DLT, "E2AP", low_prio_cpu_mask); if (gnb_cfg.pcap_cfg.e2ap.enabled) { - e2ap_p->open(gnb_cfg.pcap_cfg.e2ap.filename.c_str()); + e2ap_p->open(gnb_cfg.pcap_cfg.e2ap.filename); } - std::unique_ptr gtpu_p = std::make_unique(PCAP_GTPU_DLT, "GTPU"); + std::unique_ptr gtpu_p = std::make_unique(PCAP_GTPU_DLT, "GTPU", low_prio_cpu_mask); if (gnb_cfg.pcap_cfg.gtpu.enabled) { gtpu_p->open(gnb_cfg.pcap_cfg.gtpu.filename); } - std::unique_ptr mac_p = std::make_unique(); + std::unique_ptr mac_p = std::make_unique(low_prio_cpu_mask); if (gnb_cfg.pcap_cfg.mac.enabled) { - mac_p->open(gnb_cfg.pcap_cfg.mac.filename.c_str()); + if (gnb_cfg.pcap_cfg.mac.type == "dlt") { + mac_p->open(gnb_cfg.pcap_cfg.mac.filename, mac_pcap_type::dlt); + } else if (gnb_cfg.pcap_cfg.mac.type == "udp") { + mac_p->open(gnb_cfg.pcap_cfg.mac.filename, mac_pcap_type::udp); + } else { + report_error("Invalid type for MAC PCAP. type={}\n", gnb_cfg.pcap_cfg.mac.type); + } } worker_manager workers{gnb_cfg}; @@ -381,10 +391,17 @@ int main(int argc, char** argv) std::unique_ptr f1u_conn = std::make_unique(); // Create IO broker. - std::unique_ptr epoll_broker = create_io_broker(io_broker_type::epoll); + io_broker_config io_broker_cfg(low_prio_cpu_mask); + std::unique_ptr epoll_broker = create_io_broker(io_broker_type::epoll, io_broker_cfg); + + // Set up the JSON log channel used by metrics. + srslog::sink& json_sink = + srslog::fetch_file_sink(gnb_cfg.metrics_cfg.json_filename, 0, false, srslog::create_json_formatter()); + srslog::log_channel& json_channel = srslog::fetch_log_channel("JSON_channel", json_sink, {}); + json_channel.set_enabled(gnb_cfg.metrics_cfg.enable_json_metrics); // Create console helper object for commands and metrics printing. - gnb_console_helper console(*epoll_broker); + gnb_console_helper console(*epoll_broker, json_channel); console.on_app_starting(); std::unique_ptr hub = std::make_unique(*workers.metrics_hub_exec); @@ -445,18 +462,20 @@ int main(int argc, char** argv) } // Create CU-UP config. - srsran::srs_cu_up::cu_up_configuration cu_up_cfg; - cu_up_cfg.cu_up_executor = workers.cu_up_exec; - cu_up_cfg.cu_up_e2_exec = workers.cu_up_e2_exec; - cu_up_cfg.gtpu_pdu_executor = workers.gtpu_pdu_exec; - cu_up_cfg.e1ap.e1ap_conn_client = &e1ap_gw; - cu_up_cfg.f1u_gateway = f1u_conn->get_f1u_cu_up_gateway(); - cu_up_cfg.epoll_broker = epoll_broker.get(); - cu_up_cfg.gtpu_pcap = gtpu_p.get(); - cu_up_cfg.timers = cu_timers; - cu_up_cfg.net_cfg.n3_bind_addr = gnb_cfg.amf_cfg.bind_addr; // TODO: rename variable to core addr + srsran::srs_cu_up::cu_up_configuration cu_up_cfg = generate_cu_up_config(gnb_cfg); + cu_up_cfg.cu_up_executor = workers.cu_up_exec; + cu_up_cfg.cu_up_e2_exec = workers.cu_up_e2_exec; + cu_up_cfg.gtpu_pdu_executor = workers.gtpu_pdu_exec; + cu_up_cfg.e1ap.e1ap_conn_client = &e1ap_gw; + cu_up_cfg.f1u_gateway = f1u_conn->get_f1u_cu_up_gateway(); + cu_up_cfg.epoll_broker = epoll_broker.get(); + cu_up_cfg.gtpu_pcap = gtpu_p.get(); + cu_up_cfg.timers = cu_timers; + cu_up_cfg.net_cfg.n3_bind_addr = gnb_cfg.amf_cfg.bind_addr; // TODO: rename variable to core addr + cu_up_cfg.net_cfg.n3_rx_max_mmsg = gnb_cfg.amf_cfg.udp_rx_max_msgs; cu_up_cfg.net_cfg.f1u_bind_addr = gnb_cfg.amf_cfg.bind_addr; // FIXME: check if this can be removed for co-located case + // create and start CU-UP std::unique_ptr cu_up_obj = create_cu_up(cu_up_cfg); cu_up_obj->start(); @@ -560,6 +579,12 @@ int main(int argc, char** argv) srslog::flush(); + if (not gnb_cfg.log_cfg.tracing_filename.empty()) { + gnb_logger.info("Closing event tracer..."); + close_trace_file(); + gnb_logger.info("Event tracer closed successfully"); + } + return 0; } diff --git a/apps/gnb/gnb_appconfig.h b/apps/gnb/gnb_appconfig.h index 0b7c681a92..2c85c849ce 100644 --- a/apps/gnb/gnb_appconfig.h +++ b/apps/gnb/gnb_appconfig.h @@ -22,6 +22,7 @@ #pragma once +#include "gnb_os_sched_affinity_manager.h" #include "srsran/adt/optional.h" #include "srsran/adt/variant.h" #include "srsran/ran/band_helper.h" @@ -37,6 +38,8 @@ #include "srsran/ran/pusch/pusch_mcs.h" #include "srsran/ran/rnti.h" #include "srsran/ran/s_nssai.h" +#include "srsran/ran/sib/system_info_config.h" +#include "srsran/ran/slot_pdu_capacity_constants.h" #include "srsran/ran/subcarrier_spacing.h" #include "srsran/support/unique_thread.h" #include @@ -63,6 +66,11 @@ struct prach_appconfig { /// with the PUCCH, the user should leave some guardband between the PUCCH CRBs and the PRACH PRBs. /// Possible values: {0,...,MAX_NOF_PRB - 1}. optional prach_frequency_start; + /// Max number of RA preamble transmissions performed before declaring a failure. Values {3, 4, 5, 6, 7, 8, 10, 20, + /// 50, 100, 200}. + uint8_t preamble_trans_max = 7; + /// Power ramping steps for PRACH. Values {0, 2, 4, 6}. + uint8_t power_ramping_step_db = 4; }; /// TDD pattern configuration. See TS 38.331, \c TDD-UL-DL-Pattern. @@ -72,7 +80,7 @@ struct tdd_ul_dl_pattern_appconfig { /// Values: {0,...,maxNrofSlots=80}. unsigned nof_dl_slots = 6; /// Values: {0,...,maxNrofSymbols-1=13}. - unsigned nof_dl_symbols = 0; + unsigned nof_dl_symbols = 8; /// Values: {0,...,maxNrofSlots=80}. unsigned nof_ul_slots = 3; /// Values: {0,...,maxNrofSymbols-1=13}. @@ -156,12 +164,14 @@ struct pdsch_appconfig { std::vector rv_sequence = {0, 2, 3, 1}; /// MCS table to use for PDSCH pdsch_mcs_table mcs_table = pdsch_mcs_table::qam64; - /// Number of antenna ports. If empty, the \c nof_ports is derived from the number of DL antennas. - optional nof_ports; /// Minimum number of RBs for Resource Allocation of UE PDSCHs. unsigned min_rb_size = 1; /// Maximum number of RBs for Resource Allocation of UE PDSCHs. unsigned max_rb_size = MAX_NOF_PRBS; + /// Maximum number of PDSCH grants per slot. + unsigned max_pdschs_per_slot = MAX_PDSCH_PDUS_PER_SLOT; + /// Maximum number of DL or UL PDCCH allocation attempts per slot. + unsigned max_pdcch_alloc_attempts_per_slot = std::max(MAX_DL_PDCCH_PDUS_PER_SLOT, MAX_UL_PDCCH_PDUS_PER_SLOT); /// CQI offset increment used in outer loop link adaptation (OLLA) algorithm. If set to zero, OLLA is disabled. float olla_cqi_inc{0.001}; /// DL Target BLER to be achieved with OLLA. @@ -174,6 +184,12 @@ struct pdsch_appconfig { /// The numerology of the active DL BWP is used as a reference to determine the number of subcarriers. /// The DC offset value 0 corresponds to the center of the SCS-Carrier for the numerology of the active DL BWP. optional dc_offset; + /// Link Adaptation (LA) threshold for drop in CQI of the first HARQ transmission above which HARQ retransmissions are + /// cancelled. + uint8_t harq_la_cqi_drop_threshold{3}; + /// Link Adaptation (LA) threshold for drop in nof. layers of the first HARQ transmission above which HARQ + /// retransmission is cancelled. + uint8_t harq_la_ri_drop_threshold{1}; }; /// PUSCH application configuration. @@ -216,6 +232,8 @@ struct pusch_appconfig { /// Minimum k2 value (distance in slots between UL PDCCH and PUSCH) that the gNB can use. Values: {1, ..., 32}. unsigned min_k2 = 4; + /// Maximum number of PUSCH grants per slot. + unsigned max_puschs_per_slot = MAX_PUSCH_PDUS_PER_SLOT; /// \brief Direct Current (DC) offset, in number of subcarriers, used in PUSCH. /// /// The numerology of the active UL BWP is used as a reference to determine the number of subcarriers. @@ -315,6 +333,51 @@ struct ssb_appconfig { ssb_pss_to_sss_epre pss_to_sss_epre = ssb_pss_to_sss_epre::dB_0; }; +/// Configuration of SIBs and SI-message scheduling. +struct sib_appconfig { + struct si_sched_info_config { + /// List of SIB indexes (sib2 => value 2 in list, sib3 => value 3 in list, ...) included in this SI message. The + /// list has at most 32 elements. + std::vector sib_mapping_info; + /// Periodicity of the SI-message in radio frames. Values: {8, 16, 32, 64, 128, 256, 512}. + unsigned si_period_rf = 32; + }; + + struct sib_ue_timers_and_constants { + /// t300 + /// Values (in ms): {100, 200, 300, 400, 600, 1000, 1500, 2000} + unsigned t300 = 1000; + /// t301 + /// Values (in ms): {100, 200, 300, 400, 600, 1000, 1500, 2000} + unsigned t301 = 1000; + /// t310 + /// Values (in ms): {0, 50, 100, 200, 500, 1000, 2000} + unsigned t310 = 1000; + /// n310 + /// Values: {1, 2, 3, 4, 6, 8, 10, 20} + unsigned n310 = 1; + /// t311 + /// Values (in ms): {1000, 3000, 5000, 10000, 15000, 20000, 30000} + unsigned t311 = 30000; + /// n311 + /// Values: {1, 2, 3, 4, 5, 6, 8, 10} + unsigned n311 = 1; + /// t319 + /// Values (in ms): {100, 200, 300, 400, 600, 1000, 1500, 2000} + unsigned t319 = 1000; + }; + + /// The length of the SI scheduling window, in slots. It is always shorter or equal to the period of the SI message. + /// Values: {5, 10, 20, 40, 80, 160, 320, 640, 1280}. + unsigned si_window_len_slots = 160; + /// List of SI-messages and associated scheduling information. + std::vector si_sched_info; + /// UE timers and constants parameters + sib_ue_timers_and_constants ue_timers_and_constants; + /// Parameters of the SIB19. + sib19_info sib19; +}; + struct csi_appconfig { /// \brief \c CSI-RS period in milliseconds. Limited by TS38.214, clause 5.1.6.1.1. Values: {10, 20, 40, 80}. unsigned csi_rs_period_msec = 20; @@ -396,6 +459,8 @@ struct base_cell_appconfig { int q_qual_min = -20; /// SSB parameters. ssb_appconfig ssb_cfg; + /// SIB parameters. + sib_appconfig sib_cfg; /// UL common configuration parameters. ul_common_appconfig ul_common_cfg; /// PDCCH configuration. @@ -453,6 +518,7 @@ struct rlc_tx_am_appconfig { uint32_t max_retx_thresh; ///< Max retx threshold int32_t poll_pdu; ///< Insert poll bit after this many PDUs int32_t poll_byte; ///< Insert poll bit after this much data (bytes) + uint32_t max_window = 0; ///< Custom parameter to limit the maximum window size for memory reasons. 0 means no limit. }; /// RLC UM RX configuration @@ -520,6 +586,7 @@ struct amf_appconfig { int sctp_rto_max = 500; int sctp_init_max_attempts = 3; int sctp_max_init_timeo = 500; + int udp_rx_max_msgs = 256; bool no_core = false; }; @@ -583,8 +650,8 @@ struct mobility_appconfig { /// \brief RRC specific configuration parameters. struct rrc_appconfig { bool force_reestablishment_fallback = false; - unsigned rrc_procedure_timeout_ms = 360; ///< Timeout for RRC procedures (default SRB maxRetxThreshold * - ///< t-PollRetransmit = 8 * 45ms = 360ms, see TS 38.331 Sec 9.2.1). + unsigned rrc_procedure_timeout_ms = 720; ///< Timeout for RRC procedures (2 * default SRB maxRetxThreshold * + ///< t-PollRetransmit = 2 * 8 * 45ms = 720ms, see TS 38.331 Sec 9.2.1). }; /// \brief Security configuration parameters. @@ -594,7 +661,8 @@ struct security_appconfig { }; struct cu_cp_appconfig { - int inactivity_timer = 7200; // in seconds + int inactivity_timer = 7200; // in seconds + int ue_context_setup_timeout_s = 2; // in seconds mobility_appconfig mobility_config; rrc_appconfig rrc_config; security_appconfig security_config; @@ -650,13 +718,19 @@ struct pcap_appconfig { } gtpu; struct { std::string filename = "/tmp/gnb_mac.pcap"; + std::string type = "udp"; bool enabled = false; } mac; }; /// Metrics report configuration. struct metrics_appconfig { - unsigned rlc_report_period = 1000; // RLC report period in ms + unsigned rlc_report_period = 1000; // RLC report period in ms + unsigned cu_cp_statistics_report_period = 1; // Statistics report period in seconds + unsigned cu_up_statistics_report_period = 1; // Statistics report period in seconds + /// JSON metrics reporting. + bool enable_json_metrics = false; + std::string json_filename = "/tmp/gnb_metrics.json"; }; /// Lower physical layer thread profiles. @@ -680,21 +754,6 @@ struct expert_upper_phy_appconfig { /// demanding cell configurations, such as using large bandwidths or higher order MIMO. Higher values also increase /// the round trip latency of the radio link. unsigned max_processing_delay_slots = 2U; - /// \brief PDSCH processor type. - /// - /// Use of there options: - /// - \c automatic: selects \c lite implementation if \c nof_pdsch_threads is one, otherwise \c concurrent, or - /// - \c generic: for using unoptimized PDSCH processing, or - /// - \c concurrent: for using a processor that processes code blocks in parallel, or - /// - \c lite: for using a memory optimized processor. - std::string pdsch_processor_type = "auto"; - /// Number of threads for encoding PDSCH concurrently. Only used if \c pdsch_processor_type is set to \c concurrent. - unsigned nof_pdsch_threads = 1; - /// Number of threads for processing PUSCH and PUCCH. It is set to 4 by default unless the available hardware - /// concurrency is limited, in which case the most suitable number of threads between one and three will be selected. - unsigned nof_ul_threads = std::min(4U, std::max(std::thread::hardware_concurrency(), 4U) - 3U); - /// Number of threads for processing PDSCH, PDCCH, NZP CSI-RS and SSB. It is set to 1 by default. - unsigned nof_dl_threads = 1; /// Number of PUSCH LDPC decoder iterations. unsigned pusch_decoder_max_iterations = 6; /// Set to true to enable the PUSCH LDPC decoder early stop. @@ -735,20 +794,6 @@ struct test_mode_appconfig { /// Expert SDR Radio Unit configuration. struct ru_sdr_expert_appconfig { - ru_sdr_expert_appconfig() - { - // Set the lower PHY thread profile according to the number of CPU cores. - if (srsran::compute_host_nof_hardware_threads() >= 8U) { - lphy_executor_profile = lower_phy_thread_profile::quad; - } else { - lphy_executor_profile = lower_phy_thread_profile::dual; - } - } - - /// \brief Lower physical layer thread profile. - /// - /// If not configured, a default value is selected based on the number of available CPU cores. - lower_phy_thread_profile lphy_executor_profile; /// System time-based throttling. See \ref lower_phy_configuration::system_time_throttling for more information. float lphy_dl_throttling = 0.0F; }; @@ -821,7 +866,7 @@ struct ru_ofh_base_cell_appconfig { /// Ta4 minimum parameter for uplink User-Plane in microseconds. unsigned Ta4_min = 85U; /// Enables the Control-Plane PRACH message signalling. - bool is_prach_control_plane_enabled = false; + bool is_prach_control_plane_enabled = true; /// \brief Downlink broadcast flag. /// /// If enabled, broadcasts the contents of a single antenna port to all downlink RU eAxCs. @@ -831,15 +876,15 @@ struct ru_ofh_base_cell_appconfig { /// Uplink compression method. std::string compression_method_ul = "bfp"; /// Uplink compression bitwidth. - unsigned compresion_bitwidth_ul = 9; + unsigned compression_bitwidth_ul = 9; /// Downlink compression method. std::string compression_method_dl = "bfp"; /// Downlink compression bitwidth. - unsigned compresion_bitwidth_dl = 9; + unsigned compression_bitwidth_dl = 9; /// PRACH compression method. std::string compression_method_prach = "bfp"; /// PRACH compression bitwidth. - unsigned compresion_bitwidth_prach = 9; + unsigned compression_bitwidth_prach = 9; /// Downlink static compression header flag. bool is_downlink_static_comp_hdr_enabled = true; /// Uplink static compression header flag. @@ -878,8 +923,6 @@ struct ru_ofh_appconfig { unsigned dl_processing_time = 400U; /// Base cell configuration for the Radio Unit. ru_ofh_base_cell_appconfig base_cell_cfg; - /// Enables the parallelization of the downlink. - bool is_downlink_parallelized = true; /// Individual Open Fronthaul cells configurations. std::vector cells = {{}}; }; @@ -889,14 +932,84 @@ struct buffer_pool_appconfig { std::size_t segment_size = 1024; }; +/// CPU affinities configuration for the gNB app. +struct cpu_affinities_appconfig { + /// L1 uplink CPU affinity mask. + gnb_os_sched_affinity_config l1_ul_cpu_cfg; + /// L1 downlink workers CPU affinity mask. + gnb_os_sched_affinity_config l1_dl_cpu_cfg; + /// L2 workers CPU affinity mask. + gnb_os_sched_affinity_config l2_cell_cpu_cfg; + /// Radio Unit workers CPU affinity mask. + gnb_os_sched_affinity_config ru_cpu_cfg; + /// Low priority workers CPU affinity mask. + gnb_os_sched_affinity_config low_priority_cpu_cfg; +}; + +/// Upper PHY thread configuration for the gNB. +struct upper_phy_threads_appconfig { + /// \brief PDSCH processor type. + /// + /// Use of there options: + /// - \c automatic: selects \c lite implementation if \c nof_pdsch_threads is one, otherwise \c concurrent, or + /// - \c generic: for using unoptimized PDSCH processing, or + /// - \c concurrent: for using a processor that processes code blocks in parallel, or + /// - \c lite: for using a memory optimized processor. + std::string pdsch_processor_type = "auto"; + /// Number of threads for encoding PDSCH concurrently. Only used if \c pdsch_processor_type is set to \c concurrent. + unsigned nof_pdsch_threads = 1; + /// \brief Number of threads for concurrent PUSCH decoding. + /// + /// If the number of PUSCH decoder threads is greater than zero, the PUSCH decoder will enqueue received soft bits and + /// process them asynchronously. Otherwise, PUSCH decoding will be performed synchronously. + /// + /// In non-real-time operations (e.g., when using ZeroMQ), setting this parameter to a non-zero value can potentially + /// introduce delays in uplink HARQ feedback. + unsigned nof_pusch_decoder_threads = 0; + /// Number of threads for processing PUSCH and PUCCH. + unsigned nof_ul_threads = 1; + /// Number of threads for processing PDSCH, PDCCH, NZP CSI-RS and SSB. It is set to 1 by default. + unsigned nof_dl_threads = 1; +}; + +/// Lower PHY thread configuration fo the gNB. +struct lower_phy_threads_appconfig { + lower_phy_threads_appconfig() + { + // Set the lower PHY thread profile according to the number of CPU cores. + if (srsran::compute_host_nof_hardware_threads() >= 8U) { + execution_profile = lower_phy_thread_profile::quad; + } else { + execution_profile = lower_phy_thread_profile::dual; + } + } + /// \brief Lower physical layer thread profile. + /// + /// If not configured, a default value is selected based on the number of available CPU cores. + lower_phy_thread_profile execution_profile; +}; + +/// Open Fronthaul thread configuration for the gNB. +struct ofh_threads_appconfig { + bool is_downlink_parallelized = true; +}; + +/// Expert threads configuration of the gNB app. +struct expert_threads_appconfig { + /// Upper PHY thread configuration of the gNB app. + upper_phy_threads_appconfig upper_threads; + /// Lower PHY thread configuration of the gNB app. + lower_phy_threads_appconfig lower_threads; + /// Open Fronthaul thread configuration of the gNB app. + ofh_threads_appconfig ofh_threads; +}; + /// Expert configuration of the gNB app. -struct expert_appconfig { - /// Enables usage of affinity profile tuned for higher performance. - bool enable_tuned_affinity_profile = false; - /// Number of threads per physical CPU. - unsigned nof_threads_per_cpu = 2; - /// Number of CPU cores reserved for non-priority tasks. - unsigned nof_cores_for_non_prio_workers = 4; +struct expert_execution_appconfig { + /// CPU affinities of the gNB app. + cpu_affinities_appconfig affinities; + /// Expert thread configuration of the gNB app. + expert_threads_appconfig threads; }; /// HAL configuration of the gNB app. @@ -956,7 +1069,7 @@ struct gnb_appconfig { buffer_pool_appconfig buffer_pool_config; /// \brief Expert configuration. - expert_appconfig expert_config; + expert_execution_appconfig expert_execution_cfg; /// \brief HAL configuration. optional hal_config; diff --git a/apps/gnb/gnb_appconfig_cli11_schema.cpp b/apps/gnb/gnb_appconfig_cli11_schema.cpp index 062d1468da..c4b14af1c9 100644 --- a/apps/gnb/gnb_appconfig_cli11_schema.cpp +++ b/apps/gnb/gnb_appconfig_cli11_schema.cpp @@ -161,6 +161,7 @@ static void configure_cli11_pcap_args(CLI::App& app, pcap_appconfig& pcap_params app.add_option("--f1ap_filename", pcap_params.f1ap.filename, "F1AP PCAP file output path")->capture_default_str(); app.add_option("--f1ap_enable", pcap_params.f1ap.enabled, "Enable F1AP packet capture")->always_capture_default(); app.add_option("--mac_filename", pcap_params.mac.filename, "MAC PCAP file output path")->capture_default_str(); + app.add_option("--mac_type", pcap_params.mac.type, "MAC PCAP pcap type (DLT or UDP)")->capture_default_str(); app.add_option("--mac_enable", pcap_params.mac.enabled, "Enable MAC packet capture")->always_capture_default(); app.add_option("--e2ap_filename", pcap_params.e2ap.filename, "E2AP PCAP file output path")->capture_default_str(); app.add_option("--e2ap_enable", pcap_params.e2ap.enabled, "Enable E2AP packet capture")->always_capture_default(); @@ -172,6 +173,21 @@ static void configure_cli11_metrics_args(CLI::App& app, metrics_appconfig& metri { app.add_option("--rlc_report_period", metrics_params.rlc_report_period, "RLC metrics report period") ->capture_default_str(); + + app.add_option("--cu_cp_statistics_report_period", + metrics_params.cu_cp_statistics_report_period, + "CU-CP statistics report period in seconds. Set this value to 0 to disable this feature") + ->capture_default_str(); + + app.add_option("--cu_up_statistics_report_period", + metrics_params.cu_up_statistics_report_period, + "CU-UP statistics report period in seconds. Set this value to 0 to disable this feature") + ->capture_default_str(); + + app.add_option("--enable_json_metrics", metrics_params.enable_json_metrics, "Enable JSON metrics reporting") + ->always_capture_default(); + app.add_option("--json_metrics_filename", metrics_params.json_filename, "JSON metrics output path") + ->capture_default_str(); } static void configure_cli11_slicing_args(CLI::App& app, s_nssai_t& slice_params) @@ -193,6 +209,7 @@ static void configure_cli11_amf_args(CLI::App& app, amf_appconfig& amf_params) app.add_option("--sctp_rto_max", amf_params.sctp_rto_max, "SCTP RTO max"); app.add_option("--sctp_init_max_attempts", amf_params.sctp_init_max_attempts, "SCTP init max attempts"); app.add_option("--sctp_max_init_timeo", amf_params.sctp_max_init_timeo, "SCTP max init timeout"); + app.add_option("--udp_max_rx_msgs", amf_params.udp_rx_max_msgs, "Maximum amount of messages RX in a single syscall"); app.add_option("--no_core", amf_params.no_core, "Allow gNB to run without a core"); } @@ -355,6 +372,12 @@ static void configure_cli11_cu_cp_args(CLI::App& app, cu_cp_appconfig& cu_cp_par ->capture_default_str() ->check(CLI::Range(1, 7200)); + app.add_option("--ue_context_setup_timeout_s", + cu_cp_params.ue_context_setup_timeout_s, + "Timeout for the reception of an InitialContextSetupRequest after an InitialUeMessage was sent to the " + "core, in seconds. If the value is reached, the UE will be released") + ->capture_default_str(); + CLI::App* mobility_subcmd = app.add_subcommand("mobility", "Mobility configuration"); configure_cli11_mobility_args(*mobility_subcmd, cu_cp_params.mobility_config); @@ -367,13 +390,6 @@ static void configure_cli11_cu_cp_args(CLI::App& app, cu_cp_appconfig& cu_cp_par static void configure_cli11_expert_phy_args(CLI::App& app, expert_upper_phy_appconfig& expert_phy_params) { - auto pdsch_processor_check = [](const std::string& value) -> std::string { - if ((value == "auto") || (value == "generic") || (value == "concurrent") || (value == "lite")) { - return {}; - } - return "Invalid PDSCH processor type. Accepted values [auto,generic,concurrent,lite]"; - }; - auto pusch_sinr_method_check = [](const std::string& value) -> std::string { if ((value == "channel_estimator") || (value == "post_equalization") || (value == "evm")) { return {}; @@ -386,21 +402,6 @@ static void configure_cli11_expert_phy_args(CLI::App& app, expert_upper_phy_appc "Maximum allowed DL processing delay in slots.") ->capture_default_str() ->check(CLI::Range(1, 30)); - app.add_option("--pdsch_processor_type", - expert_phy_params.pdsch_processor_type, - "PDSCH processor type: auto, generic, concurrent and lite.") - ->capture_default_str() - ->check(pdsch_processor_check); - app.add_option("--nof_pdsch_threads", expert_phy_params.nof_pdsch_threads, "Number of threads to encode PDSCH.") - ->capture_default_str() - ->check(CLI::Number); - app.add_option("--nof_ul_threads", expert_phy_params.nof_ul_threads, "Number of upper PHY threads to process uplink.") - ->capture_default_str() - ->check(CLI::Number); - app.add_option( - "--nof_dl_threads", expert_phy_params.nof_dl_threads, "Number of upper PHY threads to process downlink.") - ->capture_default_str() - ->check(CLI::Number); app.add_option("--pusch_dec_max_iterations", expert_phy_params.pusch_decoder_max_iterations, "Maximum number of PUSCH LDPC decoder iterations") @@ -523,17 +524,22 @@ static void configure_cli11_pdsch_args(CLI::App& app, pdsch_appconfig& pdsch_par "MCS table to use PDSCH") ->default_str("qam64") ->check(CLI::IsMember({"qam64", "qam256"}, CLI::ignore_case)); - app.add_option("--nof_ports", - pdsch_params.nof_ports, - "Number of ports for PDSCH. By default it is set to be equal to number of DL antennas") - ->capture_default_str() - ->check(CLI::IsMember({1, 2, 4})); app.add_option("--min_rb_size", pdsch_params.min_rb_size, "Minimum RB size for UE PDSCH resource allocation") ->capture_default_str() ->check(CLI::Range(1U, (unsigned)MAX_NOF_PRBS)); app.add_option("--max_rb_size", pdsch_params.max_rb_size, "Maximum RB size for UE PDSCH resource allocation") ->capture_default_str() ->check(CLI::Range(1U, (unsigned)MAX_NOF_PRBS)); + app.add_option("--max_pdschs_per_slot", + pdsch_params.max_pdschs_per_slot, + "Maximum number of PDSCH grants per slot, including SIB, RAR, Paging and UE data grants.") + ->capture_default_str() + ->check(CLI::Range(1U, (unsigned)MAX_PDSCH_PDUS_PER_SLOT)); + app.add_option("--max_alloc_attempts", + pdsch_params.max_pdcch_alloc_attempts_per_slot, + "Maximum number of DL or UL PDCCH grant allocation attempts per slot before scheduler skips the slot") + ->capture_default_str() + ->check(CLI::Range(1U, (unsigned)std::max(MAX_DL_PDCCH_PDUS_PER_SLOT, MAX_UL_PDCCH_PDUS_PER_SLOT))); app.add_option("--olla_cqi_inc_step", pdsch_params.olla_cqi_inc, "Outer-loop link adaptation (OLLA) increment value. The value 0 means that OLLA is disabled") @@ -569,6 +575,18 @@ static void configure_cli11_pdsch_args(CLI::App& app, pdsch_appconfig& pdsch_par ->capture_default_str() ->check(CLI::Range(static_cast(dc_offset_t::min), static_cast(dc_offset_t::max)) | CLI::IsMember({"outside", "undetermined", "center"})); + app.add_option("--harq_la_cqi_drop_threshold", + pdsch_params.harq_la_cqi_drop_threshold, + "Link Adaptation (LA) threshold for drop in CQI of the first HARQ transmission above which HARQ " + "retransmissions are cancelled. Set this value to 0 to disable this feature") + ->capture_default_str() + ->check(CLI::Range(0, 15)); + app.add_option("--harq_la_ri_drop_threshold", + pdsch_params.harq_la_ri_drop_threshold, + "Link Adaptation (LA) threshold for drop in nof. layers of the first HARQ transmission above which " + "HARQ retransmission is cancelled. Set this value to 0 to disable this feature") + ->capture_default_str() + ->check(CLI::Range(0, 4)); } static void configure_cli11_pusch_args(CLI::App& app, pusch_appconfig& pusch_params) @@ -633,6 +651,9 @@ static void configure_cli11_pusch_args(CLI::App& app, pusch_appconfig& pusch_par return ""; }); + app.add_option("--max_puschs_per_slot", pusch_params.max_puschs_per_slot, "Maximum number of PUSCH grants per slot") + ->capture_default_str() + ->check(CLI::Range(1U, (unsigned)MAX_PUSCH_PDUS_PER_SLOT)); app.add_option("--b_offset_ack_idx_1", pusch_params.b_offset_ack_idx_1, "betaOffsetACK-Index1 part of UCI-OnPUSCH") ->capture_default_str() ->check(CLI::Range(0, 31)); @@ -826,6 +847,99 @@ static void configure_cli11_ssb_args(CLI::App& app, ssb_appconfig& ssb_params) ->check(CLI::IsMember({0, 3})); } +static void configure_cli11_si_sched_info(CLI::App& app, sib_appconfig::si_sched_info_config& si_sched_info) +{ + app.add_option("--si_period", si_sched_info.si_period_rf, "SI message scheduling period in radio frames") + ->capture_default_str() + ->check(CLI::IsMember({8, 16, 32, 64, 128, 256, 512})); + app.add_option("--sib_mapping", + si_sched_info.sib_mapping_info, + "Mapping of SIB types to SI-messages. SIB numbers should not be repeated") + ->capture_default_str() + ->check(CLI::IsMember({19})); +} + +static void configure_cli11_sib19_args(CLI::App& app, sib19_info& sib19) +{ + app.add_option("--distance_thres", sib19.distance_thres, "Distance threshold for SIB19") + ->capture_default_str() + ->check(CLI::Range(0, 255)); + // TODO: Add remaining parameters. +} + +static void configure_cli11_sib_args(CLI::App& app, sib_appconfig& sib_params) +{ + app.add_option( + "--si_window_length", + sib_params.si_window_len_slots, + "The length of the SI scheduling window, in slots. It must be always shorter or equal to the period of " + "the SI message.") + ->capture_default_str() + ->check(CLI::IsMember({5, 10, 20, 40, 80, 160, 320, 640, 1280})); + + CLI::App* sib19_subcmd = app.add_subcommand("sib19", "Content of SIB19"); + configure_cli11_sib19_args(*sib19_subcmd, sib_params.sib19); + + // SI message scheduling parameters. + app.add_option_function>( + "--si_sched_info", + [&sib_params](const std::vector& values) { + sib_params.si_sched_info.resize(values.size()); + + for (unsigned i = 0, e = values.size(); i != e; ++i) { + CLI::App subapp("SI-message scheduling information"); + subapp.config_formatter(create_yaml_config_parser()); + subapp.allow_config_extras(CLI::config_extras_mode::error); + configure_cli11_si_sched_info(subapp, sib_params.si_sched_info[i]); + std::istringstream ss(values[i]); + subapp.parse_from_stream(ss); + } + }, + "Configures the scheduling for each of the SI-messages broadcast by the gNB"); + + app.add_option("--t300", + sib_params.ue_timers_and_constants.t300, + "RRC Connection Establishment timer in ms. The timer starts upon transmission of RRCSetupRequest.") + ->capture_default_str() + ->check(CLI::IsMember({100, 200, 300, 400, 600, 1000, 1500, 2000})); + app.add_option("--t301", + sib_params.ue_timers_and_constants.t301, + "RRC Connection Re-establishment timer in ms. The timer starts upon transmission of " + "RRCReestablishmentRequest.") + ->capture_default_str() + ->check(CLI::IsMember({100, 200, 300, 400, 600, 1000, 1500, 2000})); + app.add_option("--t310", + sib_params.ue_timers_and_constants.t310, + "Out-of-sync timer in ms. The timer starts upon detecting physical layer problems for the SpCell i.e. " + "upon receiving N310 consecutive out-of-sync indications from lower layers.") + ->capture_default_str() + ->check(CLI::IsMember({0, 50, 100, 200, 500, 1000, 2000})); + app.add_option("--n310", + sib_params.ue_timers_and_constants.n310, + "Out-of-sync counter. The counter is increased upon reception of \"out-of-sync\" from lower layer " + "while the timer T310 is stopped. Starts the timer T310, when configured value is reached.") + ->capture_default_str() + ->check(CLI::IsMember({1, 2, 3, 4, 6, 8, 10, 20})); + app.add_option("--t311", + sib_params.ue_timers_and_constants.t311, + "RRC Connection Re-establishment procedure timer in ms. The timer starts upon initiating the RRC " + "connection re-establishment procedure.") + ->capture_default_str() + ->check(CLI::IsMember({1000, 3000, 5000, 10000, 15000, 20000, 30000})); + app.add_option("--n311", + sib_params.ue_timers_and_constants.n311, + "In-sync counter. The counter is increased upon reception of the \"in-sync\" from lower layer while " + "the timer T310 is running. Stops the timer T310, when configured value is reached.") + ->capture_default_str() + ->check(CLI::IsMember({1, 2, 3, 4, 5, 6, 8, 10})); + app.add_option("--t319", + sib_params.ue_timers_and_constants.t319, + "RRC Connection Resume timer in ms. The timer starts upon transmission of RRCResumeRequest " + "or RRCResumeRequest1.") + ->capture_default_str() + ->check(CLI::IsMember({100, 200, 300, 400, 600, 1000, 1500, 2000})); +} + static void configure_cli11_prach_args(CLI::App& app, prach_appconfig& prach_params) { app.add_option("--prach_config_index", @@ -868,6 +982,14 @@ static void configure_cli11_prach_args(CLI::App& app, prach_appconfig& prach_par return ""; }); + app.add_option("--preamble_trans_max", + prach_params.preamble_trans_max, + "Max number of RA preamble transmissions performed before declaring a failure") + ->capture_default_str() + ->check(CLI::IsMember({3, 4, 5, 6, 7, 8, 10, 20, 50, 100, 200})); + app.add_option("--power_ramping_step_db", prach_params.power_ramping_step_db, "Power ramping steps for PRACH") + ->capture_default_str() + ->check(CLI::IsMember({0, 2, 4, 6})); } static void configure_cli11_amplitude_control_args(CLI::App& app, amplitude_control_appconfig& amplitude_params) @@ -1105,6 +1227,10 @@ static void configure_cli11_common_cell_args(CLI::App& app, base_cell_appconfig& CLI::App* ssb_subcmd = app.add_subcommand("ssb", "SSB parameters"); configure_cli11_ssb_args(*ssb_subcmd, cell_params.ssb_cfg); + // SIB configuration. + CLI::App* sib_subcmd = app.add_subcommand("sib", "SIB configuration parameters"); + configure_cli11_sib_args(*sib_subcmd, cell_params.sib_cfg); + // UL common configuration. CLI::App* ul_common_subcmd = app.add_subcommand("ul_common", "UL common parameters"); configure_cli11_ul_common_args(*ul_common_subcmd, cell_params.ul_common_cfg); @@ -1178,6 +1304,12 @@ static void configure_cli11_rlc_am_args(CLI::App& app, rlc_am_appconfig& rlc_am_ ->capture_default_str(); rlc_tx_am_subcmd->add_option("--poll-pdu", rlc_am_params.tx.poll_pdu, "RLC AM TX PollPdu")->capture_default_str(); rlc_tx_am_subcmd->add_option("--poll-byte", rlc_am_params.tx.poll_byte, "RLC AM TX PollByte")->capture_default_str(); + rlc_tx_am_subcmd + ->add_option("--max_window", + rlc_am_params.tx.max_window, + "Non-standard parameter that limits the tx window size. Can be used for limiting memory usage with " + "large windows. 0 means no limits other than the SN size (i.e. 2^[sn_size-1]).") + ->capture_default_str(); CLI::App* rlc_rx_am_subcmd = app.add_subcommand("rx", "AM RX parameters"); rlc_rx_am_subcmd->add_option("--sn", rlc_am_params.rx.sn_field_length, "RLC AM RX SN")->capture_default_str(); rlc_rx_am_subcmd->add_option("--t-reassembly", rlc_am_params.rx.t_reassembly, "RLC AM RX t-Reassembly") @@ -1319,26 +1451,6 @@ static void configure_cli11_ru_sdr_expert_args(CLI::App& app, ru_sdr_expert_appc config.lphy_dl_throttling, "Throttles the lower PHY DL baseband generation. The range is (0, 1). Set it to zero to disable it.") ->capture_default_str(); - - app.add_option_function( - "--low_phy_thread_profile", - [&config](const std::string& value) { - if (value == "single") { - config.lphy_executor_profile = lower_phy_thread_profile::single; - } else if (value == "dual") { - config.lphy_executor_profile = lower_phy_thread_profile::dual; - } else if (value == "quad") { - config.lphy_executor_profile = lower_phy_thread_profile::quad; - } - }, - "Lower physical layer executor profile [single, dual, quad].") - ->check([](const std::string& value) -> std::string { - if ((value == "single") || (value == "dual") || (value == "quad")) { - return ""; - } - - return "Invalid executor profile. Valid profiles are: single, dual and quad."; - }); } static void configure_cli11_ru_sdr_args(CLI::App& app, ru_sdr_appconfig& config) @@ -1492,19 +1604,19 @@ static void configure_cli11_ru_ofh_base_cell_args(CLI::App& app, ru_ofh_base_cel app.add_option("--compr_method_ul", config.compression_method_ul, "Uplink compression method") ->capture_default_str() ->check(compression_method_check); - app.add_option("--compr_bitwidth_ul", config.compresion_bitwidth_ul, "Uplink compression bit width") + app.add_option("--compr_bitwidth_ul", config.compression_bitwidth_ul, "Uplink compression bit width") ->capture_default_str() ->check(CLI::Range(1, 16)); app.add_option("--compr_method_dl", config.compression_method_dl, "Downlink compression method") ->capture_default_str() ->check(compression_method_check); - app.add_option("--compr_bitwidth_dl", config.compresion_bitwidth_dl, "Downlink compression bit width") + app.add_option("--compr_bitwidth_dl", config.compression_bitwidth_dl, "Downlink compression bit width") ->capture_default_str() ->check(CLI::Range(1, 16)); app.add_option("--compr_method_prach", config.compression_method_prach, "PRACH compression method") ->capture_default_str() ->check(compression_method_check); - app.add_option("--compr_bitwidth_prach", config.compresion_bitwidth_prach, "PRACH compression bit width") + app.add_option("--compr_bitwidth_prach", config.compression_bitwidth_prach, "PRACH compression bit width") ->capture_default_str() ->check(CLI::Range(1, 16)); app.add_option("--enable_ul_static_compr_hdr", @@ -1556,9 +1668,6 @@ static void configure_cli11_ru_ofh_args(CLI::App& app, ru_ofh_appconfig& config) { app.add_option("--gps_alpha", config.gps_Alpha, "GPS Alpha")->capture_default_str()->check(CLI::Range(0.0, 1.2288e7)); app.add_option("--gps_beta", config.gps_Beta, "GPS Beta")->capture_default_str()->check(CLI::Range(-32768, 32767)); - app.add_option( - "--enable_dl_parallelization", config.is_downlink_parallelized, "Open Fronthaul downlink parallelization flag") - ->capture_default_str(); // Common cell parameters. configure_cli11_ru_ofh_base_cell_args(app, config.base_cell_cfg); @@ -1584,19 +1693,7 @@ static void configure_cli11_ru_ofh_args(CLI::App& app, ru_ofh_appconfig& config) "Sets the cell configuration on a per cell basis, overwriting the default configuration defined by cell_cfg"); } -static void parse_ru_ofh_config(CLI::App& app, ru_ofh_appconfig& ofh_cfg) -{ - CLI::App* ru_ofh_subcmd = app.add_subcommand("ru_ofh", "Open Fronthaul Radio Unit configuration")->configurable(); - configure_cli11_ru_ofh_args(*ru_ofh_subcmd, ofh_cfg); -} - -static void parse_ru_sdr_config(CLI::App& app, ru_sdr_appconfig& sdr_cfg) -{ - CLI::App* ru_sdr_subcmd = app.add_subcommand("ru_sdr", "SDR Radio Unit configuration")->configurable(); - configure_cli11_ru_sdr_args(*ru_sdr_subcmd, sdr_cfg); -} - -static void parse_buffer_pool_config(CLI::App& app, buffer_pool_appconfig& config) +static void configure_cli11_buffer_pool_args(CLI::App& app, buffer_pool_appconfig& config) { app.add_option("--nof_segments", config.nof_segments, "Number of segments allocated by the buffer pool") ->capture_default_str(); @@ -1604,27 +1701,258 @@ static void parse_buffer_pool_config(CLI::App& app, buffer_pool_appconfig& confi ->capture_default_str(); } -static void parse_hal_config(CLI::App& app, optional& config) +static void configure_cli11_hal_args(CLI::App& app, optional& config) { config.emplace(); app.add_option("--eal_args", config->eal_args, "EAL configuration parameters used to initialize DPDK"); } -static void parse_expert_config(CLI::App& app, expert_appconfig& config) +static error_type is_valid_cpu_index(unsigned cpu_idx) { - app.add_option("--enable_tuned_affinity_profile", - config.enable_tuned_affinity_profile, - "Enable usage of tuned affinity profile") - ->capture_default_str(); - app.add_option("--number_of_threads_per_cpu", config.nof_threads_per_cpu, "Number of threads per physical CPU") + unsigned nof_cpus = compute_host_nof_hardware_threads(); + if (cpu_idx >= nof_cpus) { + return fmt::format("Invalid CPU core selected '{}'. Valid range is [{}-{}]", cpu_idx, 0, nof_cpus - 1); + } + + return default_success_t(); +} + +static expected parse_one_cpu(const std::string& value) +{ + expected result = parse_int(value); + + if (result.is_error()) { + return fmt::format("Could not parse '{}' string as a CPU index", value); + } + + error_type validation_result = is_valid_cpu_index(result.value()); + if (validation_result.is_error()) { + return validation_result.error(); + } + + return result.value(); +} + +static expected, std::string> parse_cpu_range(const std::string& value) +{ + std::vector range; + std::stringstream ss(value); + while (ss.good()) { + std::string str; + getline(ss, str, '-'); + auto parse_result = parse_one_cpu(str); + if (parse_result.is_error()) { + return fmt::format("{}. Could not parse '{}' as a range", parse_result.error(), value); + } + + range.push_back(parse_result.value()); + } + + // A range is defined by two numbers. + if (range.size() != 2) { + return fmt::format("Could not parse '{}' as a range", value); + } + + if (range[1] <= range[0]) { + return fmt::format("Invalid CPU core range detected [{}-{}]", range[0], range[1]); + } + + return interval(range[0], range[1]); +} + +static void configure_cli11_affinity_args(CLI::App& app, cpu_affinities_appconfig& config) +{ + auto parsing_fcn = [](os_sched_affinity_bitmask& mask, const std::string& value, const std::string& property_name) { + std::stringstream ss(value); + + while (ss.good()) { + std::string str; + getline(ss, str, ','); + if (str.find('-') != std::string::npos) { + auto range = parse_cpu_range(str); + if (range.is_error()) { + report_error("{} in the '{}' property", range.error(), property_name); + } + + // Add 1 to the stop value as the fill method excludes the end position. + mask.fill(range.value().start(), range.value().stop() + 1); + } else { + auto cpu_idx = parse_one_cpu(str); + if (cpu_idx.is_error()) { + report_error("{} in the '{}' property", cpu_idx.error(), property_name); + } + + mask.set(cpu_idx.value()); + } + } + }; + + app.add_option_function( + "--l1_dl_cpus", + [&config, &parsing_fcn](const std::string& value) { + parsing_fcn(config.l1_dl_cpu_cfg.mask, value, "l1_dl_cpus"); + }, + "CPU cores assigned to L1 downlink tasks"); + + app.add_option_function( + "--l1_ul_cpus", + [&config, &parsing_fcn](const std::string& value) { + parsing_fcn(config.l1_ul_cpu_cfg.mask, value, "l1_ul_cpus"); + }, + "CPU cores assigned to L1 uplink tasks"); + + app.add_option_function( + "--l2_cell_cpus", + [&config, &parsing_fcn](const std::string& value) { + parsing_fcn(config.l2_cell_cpu_cfg.mask, value, "l2_cell_cpus"); + }, + "CPU cores assigned to L2 cell tasks"); + + app.add_option_function( + "--low_priority_cpus", + [&config, &parsing_fcn](const std::string& value) { + parsing_fcn(config.low_priority_cpu_cfg.mask, value, "low_priority_cpus"); + }, + "CPU cores assigned to low priority tasks"); + + app.add_option_function( + "--ru_cpus", + [&config, &parsing_fcn](const std::string& value) { parsing_fcn(config.ru_cpu_cfg.mask, value, "ru_cpus"); }, + "Number of CPUs used for the Radio Unit tasks"); + + app.add_option_function( + "--l1_dl_pinning", + [&config](const std::string& value) { + config.l1_dl_cpu_cfg.pinning_policy = to_affinity_mask_policy(value); + if (config.l1_dl_cpu_cfg.pinning_policy == gnb_sched_affinity_mask_policy::last) { + report_error("Incorrect value={} used in {} property", value, "l1_dl_pinning"); + } + }, + "Policy used for assigning CPU cores to L1 downlink tasks"); + + app.add_option_function( + "--l1_ul_pinning", + [&config](const std::string& value) { + config.l1_ul_cpu_cfg.pinning_policy = to_affinity_mask_policy(value); + if (config.l1_ul_cpu_cfg.pinning_policy == gnb_sched_affinity_mask_policy::last) { + report_error("Incorrect value={} used in {} property", value, "l1_ul_pinning"); + } + }, + "Policy used for assigning CPU cores to L1 uplink tasks"); + + app.add_option_function( + "--l2_cell_pinning", + [&config](const std::string& value) { + config.l2_cell_cpu_cfg.pinning_policy = to_affinity_mask_policy(value); + if (config.l2_cell_cpu_cfg.pinning_policy == gnb_sched_affinity_mask_policy::last) { + report_error("Incorrect value={} used in {} property", value, "l2_cell_pinning"); + } + }, + "Policy used for assigning CPU cores to L2 cell tasks"); + + app.add_option_function( + "--low_priority_pinning", + [&config](const std::string& value) { + config.low_priority_cpu_cfg.pinning_policy = to_affinity_mask_policy(value); + if (config.low_priority_cpu_cfg.pinning_policy == gnb_sched_affinity_mask_policy::last) { + report_error("Incorrect value={} used in {} property", value, "low_priority_pinning"); + } + }, + "Policy used for assigning CPU cores to low priority tasks"); + + app.add_option_function( + "--ru_pinning", + [&config](const std::string& value) { + config.ru_cpu_cfg.pinning_policy = to_affinity_mask_policy(value); + if (config.ru_cpu_cfg.pinning_policy == gnb_sched_affinity_mask_policy::last) { + report_error("Incorrect value={} used in {} property", value, "ru_pinning"); + } + }, + "Policy used for assigning CPU cores to the Radio Unit tasks"); +} + +static void configure_cli11_upper_phy_threads_args(CLI::App& app, upper_phy_threads_appconfig& config) +{ + auto pdsch_processor_check = [](const std::string& value) -> std::string { + if ((value == "auto") || (value == "generic") || (value == "concurrent") || (value == "lite")) { + return {}; + } + return "Invalid PDSCH processor type. Accepted values [auto,generic,concurrent,lite]"; + }; + + app.add_option("--pdsch_processor_type", + config.pdsch_processor_type, + "PDSCH processor type: auto, generic, concurrent and lite.") ->capture_default_str() - ->check(CLI::Range(1, 2)); - app.add_option("--number_of_reserved_cores", - config.nof_cores_for_non_prio_workers, - "Number of CPU cores reserved for non-priority tasks") + ->check(pdsch_processor_check); + app.add_option("--nof_pdsch_threads", config.nof_pdsch_threads, "Number of threads to encode PDSCH.") ->capture_default_str() - ->check(CLI::Range(0, 1024)); + ->check(CLI::Number); + app.add_option("--nof_pusch_decoder_threads", config.nof_pusch_decoder_threads, "Number of threads to decode PUSCH.") + ->capture_default_str() + ->check(CLI::Number); + app.add_option("--nof_ul_threads", config.nof_ul_threads, "Number of upper PHY threads to process uplink.") + ->capture_default_str() + ->check(CLI::Number); + app.add_option("--nof_dl_threads", config.nof_dl_threads, "Number of upper PHY threads to process downlink.") + ->capture_default_str() + ->check(CLI::Number); +} + +static void configure_cli11_lower_phy_threads_args(CLI::App& app, lower_phy_threads_appconfig& config) +{ + app.add_option_function( + "--execution_profile", + [&config](const std::string& value) { + if (value == "single") { + config.execution_profile = lower_phy_thread_profile::single; + } else if (value == "dual") { + config.execution_profile = lower_phy_thread_profile::dual; + } else if (value == "quad") { + config.execution_profile = lower_phy_thread_profile::quad; + } + }, + "Lower physical layer executor profile [single, dual, quad].") + ->check([](const std::string& value) -> std::string { + if ((value == "single") || (value == "dual") || (value == "quad")) { + return ""; + } + + return "Invalid executor profile. Valid profiles are: single, dual and quad."; + }); +} + +static void configure_cli11_ofh_threads_args(CLI::App& app, ofh_threads_appconfig& config) +{ + app.add_option( + "--enable_dl_parallelization", config.is_downlink_parallelized, "Open Fronthaul downlink parallelization flag") + ->capture_default_str(); +} + +static void configure_cli11_expert_execution_args(CLI::App& app, expert_execution_appconfig& config) +{ + // Affinity section. + CLI::App* affinity_subcmd = app.add_subcommand("affinities", "CPU affinities")->configurable(); + configure_cli11_affinity_args(*affinity_subcmd, config.affinities); + + // Threads section. + CLI::App* threads_subcmd = app.add_subcommand("threads", "Threads configuration")->configurable(); + + // Upper PHY threads. + CLI::App* upper_phy_threads_subcmd = + threads_subcmd->add_subcommand("upper_phy", "Upper PHY thread configuration")->configurable(); + configure_cli11_upper_phy_threads_args(*upper_phy_threads_subcmd, config.threads.upper_threads); + + // Lower PHY threads. + CLI::App* lower_phy_threads_subcmd = + threads_subcmd->add_subcommand("lower_phy", "Lower PHY thread configuration")->configurable(); + configure_cli11_lower_phy_threads_args(*lower_phy_threads_subcmd, config.threads.lower_threads); + + // OFH threads. + CLI::App* ofh_threads_subcmd = + threads_subcmd->add_subcommand("ofh", "Open Fronthaul thread configuration")->configurable(); + configure_cli11_ofh_threads_args(*ofh_threads_subcmd, config.threads.ofh_threads); } static void manage_ru_variant(CLI::App& app, @@ -1695,9 +2023,12 @@ void srsran::configure_cli11_with_gnb_appconfig_schema(CLI::App& app, gnb_appcon // variable, but as it is requested later, the variable needs to be static. // RU section. static ru_ofh_appconfig ofh_cfg; - parse_ru_ofh_config(app, ofh_cfg); + CLI::App* ru_ofh_subcmd = app.add_subcommand("ru_ofh", "Open Fronthaul Radio Unit configuration")->configurable(); + configure_cli11_ru_ofh_args(*ru_ofh_subcmd, ofh_cfg); + static ru_sdr_appconfig sdr_cfg; - parse_ru_sdr_config(app, sdr_cfg); + CLI::App* ru_sdr_subcmd = app.add_subcommand("ru_sdr", "SDR Radio Unit configuration")->configurable(); + configure_cli11_ru_sdr_args(*ru_sdr_subcmd, sdr_cfg); // Common cell parameters. CLI::App* common_cell_subcmd = app.add_subcommand("cell_cfg", "Default cell configuration")->configurable(); @@ -1774,18 +2105,18 @@ void srsran::configure_cli11_with_gnb_appconfig_schema(CLI::App& app, gnb_appcon // Buffer pool section. CLI::App* buffer_pool_subcmd = app.add_subcommand("buffer_pool", "Buffer pool configuration")->configurable(); - parse_buffer_pool_config(*buffer_pool_subcmd, gnb_cfg.buffer_pool_config); + configure_cli11_buffer_pool_args(*buffer_pool_subcmd, gnb_cfg.buffer_pool_config); // Test mode section. CLI::App* test_mode_subcmd = app.add_subcommand("test_mode", "Test mode configuration")->configurable(); configure_cli11_test_mode_args(*test_mode_subcmd, gnb_cfg.test_mode_cfg); // Expert section. - CLI::App* expert_subcmd = app.add_subcommand("expert", "Expert configuration")->configurable(); - parse_expert_config(*expert_subcmd, gnb_cfg.expert_config); + CLI::App* expert_subcmd = app.add_subcommand("expert_execution", "Expert execution configuration")->configurable(); + configure_cli11_expert_execution_args(*expert_subcmd, gnb_cfg.expert_execution_cfg); CLI::App* hal_subcmd = app.add_subcommand("hal", "HAL configuration")->configurable(); - parse_hal_config(*hal_subcmd, gnb_cfg.hal_config); + configure_cli11_hal_args(*hal_subcmd, gnb_cfg.hal_config); app.callback([&]() { manage_ru_variant(app, gnb_cfg, sdr_cfg, ofh_cfg); diff --git a/apps/gnb/gnb_appconfig_translators.cpp b/apps/gnb/gnb_appconfig_translators.cpp index 4474f11104..06c27f6514 100644 --- a/apps/gnb/gnb_appconfig_translators.cpp +++ b/apps/gnb/gnb_appconfig_translators.cpp @@ -125,7 +125,9 @@ srs_cu_cp::cu_cp_configuration srsran::generate_cu_cp_config(const gnb_appconfig config.cu_cp_cfg.security_config.confidentiality_protection); } - out_cfg.ue_config.inactivity_timer = std::chrono::seconds{config.cu_cp_cfg.inactivity_timer}; + out_cfg.ue_config.inactivity_timer = std::chrono::seconds{config.cu_cp_cfg.inactivity_timer}; + out_cfg.ngap_config.ue_context_setup_timeout_s = std::chrono::seconds{config.cu_cp_cfg.ue_context_setup_timeout_s}; + out_cfg.statistics_report_period = std::chrono::seconds{config.metrics_cfg.cu_cp_statistics_report_period}; out_cfg.mobility_config.mobility_manager_config.trigger_handover_from_measurements = config.cu_cp_cfg.mobility_config.trigger_handover_from_measurements; @@ -255,6 +257,14 @@ srs_cu_cp::cu_cp_configuration srsran::generate_cu_cp_config(const gnb_appconfig return out_cfg; } +srs_cu_up::cu_up_configuration srsran::generate_cu_up_config(const gnb_appconfig& config) +{ + srs_cu_up::cu_up_configuration out_cfg; + out_cfg.statistics_report_period = std::chrono::seconds{config.metrics_cfg.cu_up_statistics_report_period}; + + return out_cfg; +} + static pcch_config generate_pcch_config(const base_cell_appconfig& cell) { pcch_config cfg{}; @@ -308,11 +318,6 @@ static unsigned get_nof_rbs(const base_cell_appconfig& cell_cfg) cell_cfg.channel_bw_mhz, cell_cfg.common_scs, band_helper::get_freq_range(*cell_cfg.band)); } -static unsigned get_nof_dl_ports(const base_cell_appconfig& cell_cfg) -{ - return cell_cfg.pdsch_cfg.nof_ports.has_value() ? *cell_cfg.pdsch_cfg.nof_ports : cell_cfg.nof_antennas_dl; -} - static tdd_ul_dl_config_common generate_tdd_pattern(subcarrier_spacing scs, const tdd_ul_dl_appconfig& config) { tdd_ul_dl_config_common out; @@ -342,7 +347,7 @@ static void fill_csi_resources(serving_cell_config& out_cell, const base_cell_ap csi_helper::csi_builder_params csi_params{}; csi_params.pci = cell_cfg.pci; csi_params.nof_rbs = get_nof_rbs(cell_cfg); - csi_params.nof_ports = get_nof_dl_ports(cell_cfg); + csi_params.nof_ports = cell_cfg.nof_antennas_dl; csi_params.csi_rs_period = static_cast(csi_cfg.csi_rs_period_msec * get_nof_slots_per_subframe(cell_cfg.common_scs)); if (cell_cfg.tdd_ul_dl_cfg.has_value()) { @@ -407,7 +412,7 @@ std::vector srsran::generate_du_cell_config(const gnb_appconfig& param.band = *base_cell.band; // Enable CSI-RS if the PDSCH mcs is dynamic (min_ue_mcs != max_ue_mcs). param.csi_rs_enabled = cell.cell.pdsch_cfg.min_ue_mcs != cell.cell.pdsch_cfg.max_ue_mcs; - param.nof_dl_ports = get_nof_dl_ports(base_cell); + param.nof_dl_ports = base_cell.nof_antennas_dl; param.search_space0_index = base_cell.pdcch_cfg.common.ss0_index; param.min_k1 = base_cell.pucch_cfg.min_k1; param.min_k2 = base_cell.pusch_cfg.min_k2; @@ -466,6 +471,43 @@ std::vector srsran::generate_du_cell_config(const gnb_appconfig& out_cell.ssb_cfg.ssb_block_power = base_cell.ssb_cfg.ssb_block_power; out_cell.ssb_cfg.pss_to_sss_epre = base_cell.ssb_cfg.pss_to_sss_epre; + // SI message config. + if (not base_cell.sib_cfg.si_sched_info.empty()) { + out_cell.si_config.emplace(); + out_cell.si_config->si_window_len_slots = base_cell.sib_cfg.si_window_len_slots; + out_cell.si_config->si_sched_info.resize(base_cell.sib_cfg.si_sched_info.size()); + std::vector sibs_included; + for (unsigned i = 0; i != base_cell.sib_cfg.si_sched_info.size(); ++i) { + auto& out_si = out_cell.si_config->si_sched_info[i]; + out_si.si_period_radio_frames = base_cell.sib_cfg.si_sched_info[i].si_period_rf; + out_si.sib_mapping_info.resize(base_cell.sib_cfg.si_sched_info[i].sib_mapping_info.size()); + for (unsigned j = 0; j != base_cell.sib_cfg.si_sched_info[i].sib_mapping_info.size(); ++j) { + sibs_included.push_back(base_cell.sib_cfg.si_sched_info[i].sib_mapping_info[j]); + out_si.sib_mapping_info[j] = static_cast(sibs_included.back()); + } + } + for (const uint8_t sib_id : sibs_included) { + sib_info item; + switch (sib_id) { + case 19: { + item = base_cell.sib_cfg.sib19; + } break; + default: + report_error("SIB{} not supported\n", sib_id); + } + out_cell.si_config->sibs.push_back(item); + } + } + + // UE timers and constants config. + out_cell.ue_timers_and_constants.t300 = std::chrono::milliseconds(base_cell.sib_cfg.ue_timers_and_constants.t300); + out_cell.ue_timers_and_constants.t301 = std::chrono::milliseconds(base_cell.sib_cfg.ue_timers_and_constants.t301); + out_cell.ue_timers_and_constants.t310 = std::chrono::milliseconds(base_cell.sib_cfg.ue_timers_and_constants.t310); + out_cell.ue_timers_and_constants.n310 = base_cell.sib_cfg.ue_timers_and_constants.n310; + out_cell.ue_timers_and_constants.t311 = std::chrono::milliseconds(base_cell.sib_cfg.ue_timers_and_constants.t311); + out_cell.ue_timers_and_constants.n311 = base_cell.sib_cfg.ue_timers_and_constants.n311; + out_cell.ue_timers_and_constants.t319 = std::chrono::milliseconds(base_cell.sib_cfg.ue_timers_and_constants.t319); + // Carrier config. out_cell.dl_carrier.nof_ant = base_cell.nof_antennas_dl; out_cell.ul_carrier.nof_ant = base_cell.nof_antennas_ul; @@ -483,8 +525,10 @@ std::vector srsran::generate_du_cell_config(const gnb_appconfig& } // PRACH config. - rach_config_common& rach_cfg = *out_cell.ul_cfg_common.init_ul_bwp.rach_cfg_common; - rach_cfg.rach_cfg_generic.prach_config_index = base_cell.prach_cfg.prach_config_index.value(); + rach_config_common& rach_cfg = *out_cell.ul_cfg_common.init_ul_bwp.rach_cfg_common; + rach_cfg.rach_cfg_generic.prach_config_index = base_cell.prach_cfg.prach_config_index.value(); + rach_cfg.rach_cfg_generic.preamble_trans_max = base_cell.prach_cfg.preamble_trans_max; + rach_cfg.rach_cfg_generic.power_ramping_step_db = base_cell.prach_cfg.power_ramping_step_db; const bool is_long_prach = is_long_preamble(prach_configuration_get(band_helper::get_freq_range(param.band.value()), band_helper::get_duplex_mode(param.band.value()), @@ -843,6 +887,7 @@ std::map srsran::generate_du_qos_config(const gnb_appc out_rlc.am.tx.max_retx_thresh = qos.rlc.am.tx.max_retx_thresh; out_rlc.am.tx.poll_pdu = qos.rlc.am.tx.poll_pdu; out_rlc.am.tx.poll_byte = qos.rlc.am.tx.poll_byte; + out_rlc.am.tx.max_window = qos.rlc.am.tx.max_window; //< RX SN if (!from_number(out_rlc.am.rx.sn_field_length, qos.rlc.am.rx.sn_field_length)) { report_error("Invalid RLC AM RX SN: 5QI={}, SN={}\n", qos.five_qi, qos.rlc.am.rx.sn_field_length); @@ -861,11 +906,12 @@ std::map srsran::generate_du_qos_config(const gnb_appc } /// Fills the given low PHY configuration from the given gnb configuration. -static void generate_low_phy_config(lower_phy_configuration& out_cfg, - const cell_appconfig& config, - const ru_sdr_appconfig& ru_cfg, - const ru_sdr_cell_appconfig& ru_cell_cfg, - unsigned max_processing_delay_slot) +static void generate_low_phy_config(lower_phy_configuration& out_cfg, + const cell_appconfig& config, + const ru_sdr_appconfig& ru_cfg, + const ru_sdr_cell_appconfig& ru_cell_cfg, + const lower_phy_threads_appconfig& low_phy_threads_cfg, + unsigned max_processing_delay_slot) { const base_cell_appconfig& cell_cfg = config.cell; out_cfg.scs = cell_cfg.common_scs; @@ -888,7 +934,7 @@ static void generate_low_phy_config(lower_phy_configuration& out_cfg, if (ru_cfg.device_driver == "zmq") { out_cfg.baseband_tx_buffer_size_policy = lower_phy_baseband_buffer_size_policy::half_slot; out_cfg.baseband_rx_buffer_size_policy = lower_phy_baseband_buffer_size_policy::half_slot; - } else if (ru_cfg.expert_cfg.lphy_executor_profile == lower_phy_thread_profile::single) { + } else if (low_phy_threads_cfg.execution_profile == lower_phy_thread_profile::single) { // For single executor, the same executor processes uplink and downlink. In this case, the processing is blocked // by the signal reception. The buffers must be smaller than a slot duration considering the downlink baseband // samples must arrive to the baseband device before the transmission time passes. @@ -1081,6 +1127,7 @@ static void generate_ru_generic_config(ru_generic_configuration& out_cfg, const config.cells_cfg[i], ru_cfg, ru_cfg.cells[i], + config.expert_execution_cfg.threads.lower_threads, config.expert_phy_cfg.max_processing_delay_slots); } } @@ -1150,11 +1197,11 @@ generate_ru_ofh_config(ru_ofh_configuration& out_cfg, const gnb_appconfig& confi sector_cfg.is_downlink_broadcast_enabled = cell_cfg.cell.is_downlink_broadcast_enabled; sector_cfg.ignore_ecpri_payload_size_field = cell_cfg.cell.ignore_ecpri_payload_size_field; sector_cfg.ul_compression_params = {ofh::to_compression_type(cell_cfg.cell.compression_method_ul), - cell_cfg.cell.compresion_bitwidth_ul}; + cell_cfg.cell.compression_bitwidth_ul}; sector_cfg.dl_compression_params = {ofh::to_compression_type(cell_cfg.cell.compression_method_dl), - cell_cfg.cell.compresion_bitwidth_dl}; + cell_cfg.cell.compression_bitwidth_dl}; sector_cfg.prach_compression_params = {ofh::to_compression_type(cell_cfg.cell.compression_method_prach), - cell_cfg.cell.compresion_bitwidth_prach}; + cell_cfg.cell.compression_bitwidth_prach}; sector_cfg.iq_scaling = cell_cfg.cell.iq_scaling; sector_cfg.tci = cell_cfg.vlan_tag; @@ -1219,8 +1266,8 @@ std::vector srsran::generate_du_low_config(const gnb_appconfig ldpc::MAX_MESSAGE_SIZE; // Assume the minimum number of codeblocks per softbuffer. const unsigned min_cb_softbuffer = 2; - // Assume that the maximum number of codeblocks is equal to the number of HARQ processes times the maximum number of - // codeblocks per slot. + // Assume that the maximum number of codeblocks is equal to the number of HARQ processes times the maximum number + // of codeblocks per slot. const unsigned max_nof_codeblocks = std::max(max_harq_process * max_nof_pusch_cb_slot, min_cb_softbuffer * max_softbuffers); @@ -1234,6 +1281,9 @@ std::vector srsran::generate_du_low_config(const gnb_appconfig const prach_configuration prach_cfg = prach_configuration_get(frequency_range::FR1, duplex, cell.prach_cfg.prach_config_index.value()); + // Maximum number of HARQ processes for a PUSCH HARQ process. + static constexpr unsigned max_nof_pusch_harq = 16; + cfg.log_level = srslog::str_to_basic_level(config.log_cfg.phy_level); cfg.enable_logging_broadcast = config.log_cfg.broadcast_enabled; cfg.rx_symbol_printer_filename = config.log_cfg.phy_rx_symbols_filename; @@ -1247,7 +1297,10 @@ std::vector srsran::generate_du_low_config(const gnb_appconfig cfg.nof_dl_processors = dl_pipeline_depth; cfg.nof_slots_ul_rg = ul_pipeline_depth; cfg.nof_ul_processors = ul_pipeline_depth; - cfg.max_ul_thread_concurrency = config.expert_phy_cfg.nof_ul_threads + 1; + cfg.max_ul_thread_concurrency = config.expert_execution_cfg.threads.upper_threads.nof_ul_threads + 1; + cfg.max_pusch_concurrency = MAX_UE_PDUS_PER_SLOT * max_nof_pusch_harq; + cfg.nof_pusch_decoder_threads = config.expert_execution_cfg.threads.upper_threads.nof_pusch_decoder_threads + + config.expert_execution_cfg.threads.upper_threads.nof_ul_threads; cfg.nof_prach_buffer = prach_pipeline_depth * nof_slots_per_subframe; cfg.max_nof_td_prach_occasions = prach_cfg.nof_occasions_within_slot; cfg.max_nof_fd_prach_occasions = 1; @@ -1294,14 +1347,20 @@ scheduler_expert_config srsran::generate_scheduler_expert_config(const gnb_appco const pdsch_appconfig& pdsch = config.common_cell_cfg.pdsch_cfg; out_cfg.ue.dl_mcs = {pdsch.min_ue_mcs, pdsch.max_ue_mcs}; out_cfg.ue.pdsch_rv_sequence.assign(pdsch.rv_sequence.begin(), pdsch.rv_sequence.end()); + out_cfg.ue.dl_harq_la_cqi_drop_threshold = pdsch.harq_la_cqi_drop_threshold; + out_cfg.ue.dl_harq_la_ri_drop_threshold = pdsch.harq_la_ri_drop_threshold; + out_cfg.ue.max_pdschs_per_slot = pdsch.max_pdschs_per_slot; + out_cfg.ue.max_pdcch_alloc_attempts_per_slot = pdsch.max_pdcch_alloc_attempts_per_slot; + out_cfg.ue.pdsch_nof_rbs = {pdsch.min_rb_size, pdsch.max_rb_size}; + out_cfg.ue.olla_dl_target_bler = pdsch.olla_target_bler; + out_cfg.ue.olla_cqi_inc = pdsch.olla_cqi_inc; + out_cfg.ue.olla_max_cqi_offset = pdsch.olla_max_cqi_offset; + const pusch_appconfig& pusch = config.common_cell_cfg.pusch_cfg; out_cfg.ue.ul_mcs = {pusch.min_ue_mcs, pusch.max_ue_mcs}; out_cfg.ue.pusch_rv_sequence.assign(pusch.rv_sequence.begin(), pusch.rv_sequence.end()); - out_cfg.ue.pdsch_nof_rbs = {pdsch.min_rb_size, pdsch.max_rb_size}; out_cfg.ue.initial_ul_dc_offset = pusch.dc_offset; - out_cfg.ue.olla_dl_target_bler = pdsch.olla_target_bler; - out_cfg.ue.olla_cqi_inc = pdsch.olla_cqi_inc; - out_cfg.ue.olla_max_cqi_offset = pdsch.olla_max_cqi_offset; + out_cfg.ue.max_puschs_per_slot = pusch.max_puschs_per_slot; out_cfg.ue.olla_ul_target_bler = pusch.olla_target_bler; out_cfg.ue.olla_ul_snr_inc = pusch.olla_snr_inc; out_cfg.ue.olla_max_ul_snr_offset = pusch.olla_max_snr_offset; diff --git a/apps/gnb/gnb_appconfig_translators.h b/apps/gnb/gnb_appconfig_translators.h index d94975a98c..94edf6f81d 100644 --- a/apps/gnb/gnb_appconfig_translators.h +++ b/apps/gnb/gnb_appconfig_translators.h @@ -23,6 +23,7 @@ #pragma once #include "srsran/cu_cp/cu_cp_configuration.h" +#include "srsran/cu_up/cu_up_configuration.h" #include "srsran/du/du_cell_config.h" #include "srsran/du/du_qos_config.h" #include "srsran/e2/e2ap_configuration.h" @@ -52,6 +53,9 @@ srsran::sctp_network_gateway_config generate_ngap_nw_config(const gnb_appconfig& /// Converts and returns the given gnb application configuration to a CU-CP configuration. srs_cu_cp::cu_cp_configuration generate_cu_cp_config(const gnb_appconfig& config); +/// Converts and returns the given gnb application configuration to a CU-UP configuration. +srs_cu_up::cu_up_configuration generate_cu_up_config(const gnb_appconfig& config); + /// Converts and returns the given gnb application configuration to a DU cell configuration. std::vector generate_du_cell_config(const gnb_appconfig& config); diff --git a/apps/gnb/gnb_appconfig_validators.cpp b/apps/gnb/gnb_appconfig_validators.cpp index 2812136d18..af31cb0fd7 100644 --- a/apps/gnb/gnb_appconfig_validators.cpp +++ b/apps/gnb/gnb_appconfig_validators.cpp @@ -111,6 +111,22 @@ static bool validate_ru_ofh_appconfig(const gnb_appconfig& config) return false; } + if (cell_cfg.nof_antennas_ul > ofh_cell.ru_ul_port_id.size()) { + fmt::print("RU number of uplink ports={} must be equal or greater than the number of reception antennas={}\n", + ofh_cell.ru_ul_port_id.size(), + cell_cfg.nof_antennas_ul); + + return false; + } + + if (cell_cfg.nof_antennas_ul > ofh_cell.ru_prach_port_id.size()) { + fmt::print("RU number of PRACH ports={} must be equal or greater than the number of reception antennas={}\n", + ofh_cell.ru_prach_port_id.size(), + cell_cfg.nof_antennas_ul); + + return false; + } + if (!validate_ru_duplicated_ports(ofh_cell.ru_dl_port_id)) { fmt::print("Detected duplicated downlink port identifiers\n"); @@ -296,6 +312,38 @@ static bool validate_dl_arfcn_and_band(const base_cell_appconfig& config) return true; } +static bool validate_cell_sib_config(const base_cell_appconfig& cell_cfg) +{ + const sib_appconfig& sib_cfg = cell_cfg.sib_cfg; + + for (const auto& si_msg : sib_cfg.si_sched_info) { + const unsigned si_period_slots = + si_msg.si_period_rf * get_nof_slots_per_subframe(cell_cfg.common_scs) * NOF_SUBFRAMES_PER_FRAME; + if (sib_cfg.si_window_len_slots > si_period_slots) { + fmt::print("The SI window length in slots {} is larger than the SI message period {}.\n", + sib_cfg.si_window_len_slots, + si_period_slots); + return false; + } + } + + // Check if there are repeated SIBs in the SI messages. + std::vector sibs_included; + for (const auto& si_msg : sib_cfg.si_sched_info) { + for (const uint8_t sib_it : si_msg.sib_mapping_info) { + sibs_included.push_back(sib_it); + } + } + std::sort(sibs_included.begin(), sibs_included.end()); + const auto duplicate_it = std::adjacent_find(sibs_included.begin(), sibs_included.end()); + if (duplicate_it != sibs_included.end()) { + fmt::print("The SIB{} cannot be included more than once in the broadcast SI messages", *duplicate_it); + return false; + } + + return true; +} + /// Validates the given cell application configuration. Returns true on success, otherwise false. static bool validate_base_cell_appconfig(const base_cell_appconfig& config) { @@ -326,6 +374,15 @@ static bool validate_base_cell_appconfig(const base_cell_appconfig& config) return false; } + const auto ssb_scs = + band_helper::get_most_suitable_ssb_scs(band_helper::get_band_from_dl_arfcn(config.dl_arfcn), config.common_scs); + if (ssb_scs != config.common_scs) { + fmt::print("Common SCS {}kHz is not equal to SSB SCS {}kHz. Different SCS for common and SSB is not supported.\n", + scs_to_khz(config.common_scs), + scs_to_khz(ssb_scs)); + return false; + } + if (!validate_pdsch_cell_app_config(config.pdsch_cfg)) { return false; } @@ -348,10 +405,7 @@ static bool validate_base_cell_appconfig(const base_cell_appconfig& config) return false; } - if (config.pdsch_cfg.nof_ports.has_value() and config.nof_antennas_dl < *config.pdsch_cfg.nof_ports) { - fmt::print("Number of PDSCH ports {} cannot be higher than the number of DL antennas {}\n", - *config.pdsch_cfg.nof_ports, - config.nof_antennas_dl); + if (!validate_cell_sib_config(config)) { return false; } @@ -504,23 +558,8 @@ static bool validate_log_appconfig(const log_appconfig& config) /// Validates expert physical layer configuration parameters. static bool validate_expert_phy_appconfig(const expert_upper_phy_appconfig& config) { - static const interval nof_ul_dl_threads_range(1, std::thread::hardware_concurrency()); - static const interval nof_pdsch_threads_range(2, std::thread::hardware_concurrency()); - bool valid = true; - if (!nof_ul_dl_threads_range.contains(config.nof_ul_threads)) { - fmt::print( - "Number of PHY UL threads (i.e., {}) must be in range {}.\n", config.nof_ul_threads, nof_ul_dl_threads_range); - valid = false; - } - - if ((config.pdsch_processor_type != "auto") && (config.pdsch_processor_type != "concurrent") && - config.pdsch_processor_type != "generic" && (config.pdsch_processor_type != "lite")) { - fmt::print("Invalid PDSCH processor type. Valid types are: auto, generic, concurrent and lite.\n"); - valid = false; - } - if ((config.pusch_sinr_calc_method != "channel_estimator") && (config.pusch_sinr_calc_method != "post_equalization") && (config.pusch_sinr_calc_method != "evm")) { fmt::print( @@ -528,35 +567,6 @@ static bool validate_expert_phy_appconfig(const expert_upper_phy_appconfig& conf valid = false; } - if ((config.pdsch_processor_type == "concurrent") && !nof_pdsch_threads_range.contains(config.nof_pdsch_threads)) { - fmt::print("For concurrent PDSCH processor. Number of PHY PDSCH threads (i.e., {}) must be in range {}.\n", - config.nof_pdsch_threads, - nof_pdsch_threads_range); - valid = false; - } else if ((config.pdsch_processor_type == "auto") && !nof_ul_dl_threads_range.contains(config.nof_pdsch_threads)) { - fmt::print("For auto PDSCH processor. Number of PHY PDSCH threads (i.e., {}) must be in range {}.\n", - config.nof_pdsch_threads, - nof_ul_dl_threads_range); - valid = false; - } else if ((config.pdsch_processor_type != "auto") && (config.pdsch_processor_type != "concurrent") && - (config.nof_pdsch_threads > 1)) { - fmt::print("Number of PHY PDSCH threads (i.e., {}) is ignored.\n", config.nof_pdsch_threads); - } - - if (!nof_ul_dl_threads_range.contains(config.nof_dl_threads)) { - fmt::print( - "Number of PHY DL threads (i.e., {}) must be in range {}.\n", config.nof_dl_threads, nof_ul_dl_threads_range); - valid = false; - } - - if (config.nof_dl_threads > config.max_processing_delay_slots) { - fmt::print("Number of PHY DL threads (i.e., {}) cannot be larger than the maximum processing delay in slots " - "(i.e., {}).\n", - config.nof_dl_threads, - config.max_processing_delay_slots); - valid = false; - } - if (config.pusch_decoder_max_iterations == 0) { fmt::print("Maximum PUSCH LDPC decoder iterations cannot be zero.\n"); valid = false; @@ -640,13 +650,10 @@ static bool validate_test_mode_appconfig(const gnb_appconfig& config) fmt::print("For test mode, RI shall not be set if UE is configured to use DCI format 1_0\n"); return false; } - unsigned nof_ports = config.common_cell_cfg.pdsch_cfg.nof_ports.has_value() - ? *config.common_cell_cfg.pdsch_cfg.nof_ports - : config.common_cell_cfg.nof_antennas_dl; - if (config.test_mode_cfg.test_ue.ri > nof_ports) { + if (config.test_mode_cfg.test_ue.ri > config.common_cell_cfg.nof_antennas_dl) { fmt::print("For test mode, RI cannot be higher than the number of DL antenna ports ({} > {})\n", config.test_mode_cfg.test_ue.ri, - config.common_cell_cfg.pdsch_cfg.nof_ports); + config.common_cell_cfg.nof_antennas_dl); return false; } @@ -716,6 +723,68 @@ static bool validate_hal_config(const optional& config) return true; } +static bool validate_upper_phy_threads_appconfig(const upper_phy_threads_appconfig& config, + unsigned max_processing_delay_slots) +{ + static const interval nof_ul_dl_threads_range(1, std::thread::hardware_concurrency()); + static const interval nof_pdsch_threads_range(2, std::thread::hardware_concurrency()); + + bool valid = true; + + if (!nof_ul_dl_threads_range.contains(config.nof_ul_threads)) { + fmt::print( + "Number of PHY UL threads (i.e., {}) must be in range {}.\n", config.nof_ul_threads, nof_ul_dl_threads_range); + valid = false; + } + + if ((config.pdsch_processor_type != "auto") && (config.pdsch_processor_type != "concurrent") && + config.pdsch_processor_type != "generic" && (config.pdsch_processor_type != "lite")) { + fmt::print("Invalid PDSCH processor type. Valid types are: auto, generic, concurrent and lite.\n"); + valid = false; + } + + if ((config.pdsch_processor_type == "concurrent") && !nof_pdsch_threads_range.contains(config.nof_pdsch_threads)) { + fmt::print("For concurrent PDSCH processor. Number of PHY PDSCH threads (i.e., {}) must be in range {}.\n", + config.nof_pdsch_threads, + nof_pdsch_threads_range); + valid = false; + } else if ((config.pdsch_processor_type == "auto") && !nof_ul_dl_threads_range.contains(config.nof_pdsch_threads)) { + fmt::print("For auto PDSCH processor. Number of PHY PDSCH threads (i.e., {}) must be in range {}.\n", + config.nof_pdsch_threads, + nof_ul_dl_threads_range); + valid = false; + } else if ((config.pdsch_processor_type != "auto") && (config.pdsch_processor_type != "concurrent") && + (config.nof_pdsch_threads > 1)) { + fmt::print("Number of PHY PDSCH threads (i.e., {}) is ignored.\n", config.nof_pdsch_threads); + } + + if (!nof_ul_dl_threads_range.contains(config.nof_dl_threads)) { + fmt::print( + "Number of PHY DL threads (i.e., {}) must be in range {}.\n", config.nof_dl_threads, nof_ul_dl_threads_range); + valid = false; + } + + if (config.nof_dl_threads > max_processing_delay_slots) { + fmt::print("Number of PHY DL threads (i.e., {}) cannot be larger than the maximum processing delay in slots " + "(i.e., {}).\n", + config.nof_dl_threads, + max_processing_delay_slots); + valid = false; + } + + return valid; +} + +static bool validate_expert_execution_appconfig(const gnb_appconfig& config) +{ + if (!validate_upper_phy_threads_appconfig(config.expert_execution_cfg.threads.upper_threads, + config.expert_phy_cfg.max_processing_delay_slots)) { + return false; + } + + return true; +} + bool srsran::validate_appconfig(const gnb_appconfig& config) { if (!validate_log_appconfig(config.log_cfg)) { @@ -800,5 +869,9 @@ bool srsran::validate_appconfig(const gnb_appconfig& config) return false; } + if (!validate_expert_execution_appconfig(config)) { + return false; + } + return true; } diff --git a/apps/gnb/gnb_du_factory.cpp b/apps/gnb/gnb_du_factory.cpp index 3696806ef8..b0ce63f107 100644 --- a/apps/gnb/gnb_du_factory.cpp +++ b/apps/gnb/gnb_du_factory.cpp @@ -35,6 +35,7 @@ static du_low_configuration create_du_low_config(const gnb_appconfig& span dl_executors, task_executor* pucch_executor, task_executor* pusch_executor, + task_executor* pusch_decoder_executor, task_executor* prach_executor, task_executor* pdsch_codeblock_executor, upper_phy_rx_symbol_request_notifier* rx_symbol_request_notifier) @@ -46,20 +47,25 @@ static du_low_configuration create_du_low_config(const gnb_appconfig& du_lo_cfg.dl_proc_cfg.ldpc_encoder_type = "auto"; du_lo_cfg.dl_proc_cfg.crc_calculator_type = "auto"; - if ((params.expert_phy_cfg.pdsch_processor_type == "lite") || - ((params.expert_phy_cfg.pdsch_processor_type == "auto") && (params.expert_phy_cfg.nof_pdsch_threads == 1))) { + const upper_phy_threads_appconfig& upper_phy_threads_cfg = params.expert_execution_cfg.threads.upper_threads; + + if ((upper_phy_threads_cfg.pdsch_processor_type == "lite") || + ((upper_phy_threads_cfg.pdsch_processor_type == "auto") && (upper_phy_threads_cfg.nof_pdsch_threads == 1))) { du_lo_cfg.dl_proc_cfg.pdsch_processor.emplace(); - } else if ((params.expert_phy_cfg.pdsch_processor_type == "concurrent") || - ((params.expert_phy_cfg.pdsch_processor_type == "auto") && - (params.expert_phy_cfg.nof_pdsch_threads > 1))) { + } else if ((upper_phy_threads_cfg.pdsch_processor_type == "concurrent") || + ((upper_phy_threads_cfg.pdsch_processor_type == "auto") && + (upper_phy_threads_cfg.nof_pdsch_threads > 1))) { pdsch_processor_concurrent_configuration pdsch_proc_config; - pdsch_proc_config.nof_pdsch_codeblock_threads = params.expert_phy_cfg.nof_pdsch_threads; + pdsch_proc_config.nof_pdsch_codeblock_threads = params.expert_execution_cfg.threads.upper_threads.nof_dl_threads + + params.expert_execution_cfg.threads.upper_threads.nof_pdsch_threads; + pdsch_proc_config.max_nof_simultaneous_pdsch = + (MAX_UE_PDUS_PER_SLOT + 1) * params.expert_phy_cfg.max_processing_delay_slots; pdsch_proc_config.pdsch_codeblock_task_executor = pdsch_codeblock_executor; du_lo_cfg.dl_proc_cfg.pdsch_processor.emplace(pdsch_proc_config); - } else if (params.expert_phy_cfg.pdsch_processor_type == "generic") { + } else if (upper_phy_threads_cfg.pdsch_processor_type == "generic") { du_lo_cfg.dl_proc_cfg.pdsch_processor.emplace(); } else { - srsran_assert(false, "Invalid PDSCH processor type {}.", params.expert_phy_cfg.pdsch_processor_type); + srsran_assert(false, "Invalid PDSCH processor type {}.", upper_phy_threads_cfg.pdsch_processor_type); } du_lo_cfg.upper_phy = generate_du_low_config(params); @@ -70,6 +76,7 @@ static du_low_configuration create_du_low_config(const gnb_appconfig& cfg.dl_executors = dl_executors; cfg.pucch_executor = pucch_executor; cfg.pusch_executor = pusch_executor; + cfg.pusch_decoder_executor = pusch_decoder_executor; cfg.prach_executor = prach_executor; cfg.rx_symbol_request_notifier = rx_symbol_request_notifier; cfg.crc_calculator_type = "auto"; @@ -109,7 +116,12 @@ std::vector> srsran::make_gnb_dus(const gnb_appconfig& } // Connect Console Aggregator to DU Scheduler UE metrics. - source_->add_subscriber(console_helper.get_metrics_notifier()); + source_->add_subscriber(console_helper.get_stdout_metrics_notifier()); + + // Connect JSON metrics reporter to DU Scheduler UE metrics. + if (gnb_cfg.metrics_cfg.enable_json_metrics) { + source_->add_subscriber(console_helper.get_json_metrics_notifier()); + } // Connect E2 agent to DU Scheduler UE metrics. if (gnb_cfg.e2_cfg.enable_du_e2) { @@ -137,6 +149,7 @@ std::vector> srsran::make_gnb_dus(const gnb_appconfig& du_low_dl_exec, workers.upper_pucch_exec[i], workers.upper_pusch_exec[i], + workers.upper_pusch_decoder_exec[i], workers.upper_prach_exec[i], workers.upper_pdsch_exec[i], &rx_symbol_request_notifier); diff --git a/apps/gnb/gnb_os_sched_affinity_manager.h b/apps/gnb/gnb_os_sched_affinity_manager.h new file mode 100644 index 0000000000..0e66c271d4 --- /dev/null +++ b/apps/gnb/gnb_os_sched_affinity_manager.h @@ -0,0 +1,166 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/support/unique_thread.h" + +namespace srsran { + +/// Types of CPU affinity masks in the gNB. +enum class gnb_sched_affinity_mask_types { l1_dl, l1_ul, l2_cell, ru, low_priority, last }; + +/// Thread pinning policy to a CPU affinity mask in the gNB. +enum class gnb_sched_affinity_mask_policy { + /// Each thread is pinned to a single CPU in a mask in RR fashion. + round_robin = 0, + /// All threads are sharing the same affinity mask. + mask = 1, + last +}; + +/// Converts the given sting into an affinity mask policy or returns last if it could not convert it. +inline gnb_sched_affinity_mask_policy to_affinity_mask_policy(const std::string& value) +{ + if (value == "round-robin") { + return gnb_sched_affinity_mask_policy::round_robin; + } + if (value == "mask") { + return gnb_sched_affinity_mask_policy::mask; + } + + return gnb_sched_affinity_mask_policy::last; +} + +/// Converts and returns the given affinity mask type to an integer. +inline unsigned to_unsigned(gnb_sched_affinity_mask_types value) +{ + return static_cast(value); +} + +/// Converts and returns the given value to an affinity mask type. +inline gnb_sched_affinity_mask_types to_affinity_mask_type(unsigned value) +{ + return static_cast(value); +} + +/// CPU affinity configuration in the gNB app. +struct gnb_os_sched_affinity_config { + /// Affinity mask. + os_sched_affinity_bitmask mask; + /// Thread pinning policy. + gnb_sched_affinity_mask_policy pinning_policy = gnb_sched_affinity_mask_policy::round_robin; +}; + +/// \brief Scheduler affinity mask manager. +/// +/// Manages the CPU affinity mask for the gNB application. It calculates and returns a mask using on a round-robin +/// algorithm on a configured mask. +class gnb_os_sched_affinity_manager +{ + /// Interface of the class managing affinity mask assignments depending on the policy. + class affinity_entry + { + public: + explicit affinity_entry(const os_sched_affinity_bitmask& mask_) : mask(mask_) {} + virtual ~affinity_entry() = default; + + virtual os_sched_affinity_bitmask calculate_mask() = 0; + + protected: + os_sched_affinity_bitmask mask; + }; + + /// Helper class that manages the mask and implements the round-robin algorithm. + class affinity_entry_rr : public affinity_entry + { + public: + explicit affinity_entry_rr(const os_sched_affinity_bitmask& mask_) : affinity_entry(mask_) {} + + /// Calculates and returns a bitmask using a round-robin algorithm over the configured mask. If no CPU is enabled in + /// the configured mask, it returns the default mask. + os_sched_affinity_bitmask calculate_mask() override + { + // No CPU is selected. + if (mask.none()) { + return {}; + } + + int pos = -1; + + while (pos < 0) { + pos = mask.find_lowest(last_pos_used, mask.size()); + + last_pos_used = (pos < 0) ? 0 : pos + 1; + } + + return os_sched_affinity_bitmask(pos); + } + + private: + unsigned last_pos_used = 0; + }; + + /// Helper class that manages the mask and return user-defined mask. + class affinity_entry_mask : public affinity_entry + { + public: + explicit affinity_entry_mask(const os_sched_affinity_bitmask& mask_) : affinity_entry(mask_) {} + + os_sched_affinity_bitmask calculate_mask() override { return mask; } + }; + +public: + gnb_os_sched_affinity_manager(const std::vector& entries) + { + srsran_assert(entries.size() == to_unsigned(gnb_sched_affinity_mask_types::last), + "Invalid number of CPU affinity masks received. Got '{}' while expected '{}' ", + entries.size(), + to_unsigned(gnb_sched_affinity_mask_types::last)); + + for (const auto& entry : entries) { + switch (entry.pinning_policy) { + case gnb_sched_affinity_mask_policy::round_robin: + cpu_masks.push_back(std::make_unique(entry.mask)); + break; + case gnb_sched_affinity_mask_policy::mask: + cpu_masks.push_back(std::make_unique(entry.mask)); + break; + default: + srsran_assert(0, "Invalid affinity mask policy={}", entry.pinning_policy); + } + } + } + + /// Calculate and returns the affinity mask for the given type. + os_sched_affinity_bitmask calcute_affinity_mask(gnb_sched_affinity_mask_types type) + { + srsran_assert(type != gnb_sched_affinity_mask_types::last, "Invalid type '{}'", to_unsigned(type)); + + return cpu_masks[to_unsigned(type)]->calculate_mask(); + } + +private: + std::vector> cpu_masks; +}; + +} // namespace srsran diff --git a/apps/gnb/gnb_worker_manager.cpp b/apps/gnb/gnb_worker_manager.cpp index e9c41cc972..d2cd4950b5 100644 --- a/apps/gnb/gnb_worker_manager.cpp +++ b/apps/gnb/gnb_worker_manager.cpp @@ -31,40 +31,42 @@ using namespace srsran; static const uint32_t task_worker_queue_size = 2048; -worker_manager::worker_manager(const gnb_appconfig& appcfg) +static gnb_os_sched_affinity_config get_mask_from_config(const cpu_affinities_appconfig& config, + gnb_sched_affinity_mask_types mask_type) { - bool is_blocking_mode_active = false; - if (variant_holds_alternative(appcfg.ru_cfg)) { - const ru_sdr_appconfig& sdr_cfg = variant_get(appcfg.ru_cfg); - is_blocking_mode_active = sdr_cfg.device_driver == "zmq"; + switch (mask_type) { + case gnb_sched_affinity_mask_types::l1_dl: + return config.l1_dl_cpu_cfg; + case gnb_sched_affinity_mask_types::l1_ul: + return config.l1_ul_cpu_cfg; + case gnb_sched_affinity_mask_types::l2_cell: + return config.l2_cell_cpu_cfg; + case gnb_sched_affinity_mask_types::ru: + return config.ru_cpu_cfg; + case gnb_sched_affinity_mask_types::low_priority: + return config.low_priority_cpu_cfg; + default: + srsran_assert(0, "Invalid affinity mask type '{}'", to_unsigned(mask_type)); } + return {}; +} - if (appcfg.expert_config.enable_tuned_affinity_profile) { - use_tuned_profile = true; - affinity_manager = std::make_unique(appcfg.expert_config.nof_threads_per_cpu, - appcfg.expert_config.nof_cores_for_non_prio_workers); - } else { - affinity_manager = std::make_unique(); - } +static std::vector build_affinity_manager_dependencies(const gnb_appconfig& config) +{ + std::vector out; - // If an OFH RU is configured, create its executors first. - if (variant_holds_alternative(appcfg.ru_cfg)) { - create_ofh_executors(appcfg.cells_cfg, variant_get(appcfg.ru_cfg).is_downlink_parallelized); - } + const cpu_affinities_appconfig& affinities_cfg = config.expert_execution_cfg.affinities; - // Select the PDSCH concurrent thread only if the PDSCH processor type is set to concurrent or auto. - unsigned nof_pdsch_threads = 1; - if ((appcfg.expert_phy_cfg.pdsch_processor_type == "concurrent") || - (appcfg.expert_phy_cfg.pdsch_processor_type == "auto")) { - nof_pdsch_threads = appcfg.expert_phy_cfg.nof_pdsch_threads; + for (unsigned i = 0, e = to_unsigned(srsran::gnb_sched_affinity_mask_types::last); i != e; ++i) { + out.push_back(get_mask_from_config(affinities_cfg, to_affinity_mask_type(i))); } - create_du_cu_executors(is_blocking_mode_active, - appcfg.expert_phy_cfg.nof_ul_threads, - appcfg.expert_phy_cfg.nof_dl_threads, - nof_pdsch_threads, - appcfg.cells_cfg, - appcfg.expert_phy_cfg.max_processing_delay_slots); + return out; +} + +worker_manager::worker_manager(const gnb_appconfig& appcfg) : affinity_mng(build_affinity_manager_dependencies(appcfg)) +{ + create_du_cu_executors(appcfg); create_ru_executors(appcfg); } @@ -83,10 +85,14 @@ void worker_manager::create_worker_pool(const std::string& { using namespace execution_config_helper; + concurrent_queue_policy queue_policy = concurrent_queue_policy::locking_mpmc; + const worker_pool pool{name, nof_workers, - {concurrent_queue_policy::locking_mpmc, queue_size}, + {queue_policy, queue_size}, execs, + queue_policy == concurrent_queue_policy::locking_mpmc ? optional{} + : std::chrono::microseconds{10}, prio, std::vector{cpu_masks.begin(), cpu_masks.end()}}; if (not exec_mng.add_execution_context(create_execution_context(pool))) { @@ -97,41 +103,50 @@ void worker_manager::create_worker_pool(const std::string& void worker_manager::create_prio_worker(const std::string& name, unsigned queue_size, const std::vector& execs, + const os_sched_affinity_bitmask& mask, os_thread_realtime_priority prio) { using namespace execution_config_helper; - const single_worker worker_desc{name, - {concurrent_queue_policy::locking_mpsc, queue_size}, - execs, - nullopt, - prio, - calculate_affinity_mask(name, prio)}; + const single_worker worker_desc{ + name, {concurrent_queue_policy::locking_mpsc, queue_size}, execs, nullopt, prio, mask}; if (not exec_mng.add_execution_context(create_execution_context(worker_desc))) { report_fatal_error("Failed to instantiate {} execution context", worker_desc.name); } } -void worker_manager::create_du_cu_executors(bool is_blocking_mode_active, - unsigned nof_ul_workers, - unsigned nof_dl_workers, - unsigned nof_pdsch_workers, - span cells_cfg, - unsigned pipeline_depth) +void worker_manager::create_du_cu_executors(const gnb_appconfig& appcfg) { using namespace execution_config_helper; + bool is_blocking_mode_active = false; + if (variant_holds_alternative(appcfg.ru_cfg)) { + const ru_sdr_appconfig& sdr_cfg = variant_get(appcfg.ru_cfg); + is_blocking_mode_active = sdr_cfg.device_driver == "zmq"; + } + + span cells_cfg = appcfg.cells_cfg; + // Worker for handling UE PDU traffic. - const single_worker gnb_ue_worker{"gnb_ue", - {concurrent_queue_policy::locking_mpsc, task_worker_queue_size}, - {{"cu_up_exec"}, {"gtpu_pdu_exec", false}, {"du_ue_exec"}, {"cu_up_e2_exec"}}, - nullopt}; + const priority_multiqueue_worker gnb_ue_worker{ + "gnb_ue", + // Three queues, one for UE UP maintenance tasks, one for UL PDUs and one for DL PDUs. + {{concurrent_queue_policy::lockfree_mpmc, task_worker_queue_size}, + {concurrent_queue_policy::lockfree_mpmc, task_worker_queue_size}, + // The IO-broker is currently single threaded, so we can use a SPSC. + {concurrent_queue_policy::lockfree_spsc, task_worker_queue_size}}, + std::chrono::microseconds{200}, + {{"ue_up_ctrl_exec", task_priority::max}, + {"ue_ul_exec", task_priority::max - 1, false}, + {"ue_dl_exec", task_priority::max - 2, false}}, + os_thread_realtime_priority::max() - 30, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::low_priority)}; if (not exec_mng.add_execution_context(create_execution_context(gnb_ue_worker))) { report_fatal_error("Failed to instantiate gNB UE execution context"); } - cu_up_exec = exec_mng.executors().at("cu_up_exec"); - gtpu_pdu_exec = exec_mng.executors().at("gtpu_pdu_exec"); - cu_up_e2_exec = exec_mng.executors().at("cu_up_e2_exec"); + cu_up_exec = exec_mng.executors().at("ue_up_ctrl_exec"); + gtpu_pdu_exec = exec_mng.executors().at("ue_dl_exec"); + cu_up_e2_exec = exec_mng.executors().at("ue_up_ctrl_exec"); // Worker for handling DU, CU and UE control procedures. const priority_multiqueue_worker gnb_ctrl_worker{ @@ -146,7 +161,7 @@ void worker_manager::create_du_cu_executors(bool is_blocki {"du_timer_exec", task_priority::max}, {"du_e2_exec", task_priority::min}}, os_thread_realtime_priority::max() - 20, - calculate_affinity_mask("gnb_ctrl", os_thread_realtime_priority::max() - 20)}; + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::l2_cell)}; if (not exec_mng.add_execution_context(create_execution_context(gnb_ctrl_worker))) { report_fatal_error("Failed to instantiate gNB control execution context"); } @@ -159,13 +174,14 @@ void worker_manager::create_du_cu_executors(bool is_blocki const std::string cell_id_str = std::to_string(cell_id); const priority_multiqueue_worker du_cell_worker{ "du_cell#" + cell_id_str, - {{concurrent_queue_policy::lockfree_spsc, 8}, {concurrent_queue_policy::locking_mpsc, task_worker_queue_size}}, + {{concurrent_queue_policy::lockfree_spsc, 8}, {concurrent_queue_policy::lockfree_mpmc, task_worker_queue_size}}, std::chrono::microseconds{10}, - // Create Cell and slot indication executors. In case of ZMQ, we make the slot indication executor synchronous. + // Create Cell and slot indication executors. In case of ZMQ, we make the slot indication executor + // synchronous. {{"cell_exec#" + cell_id_str, task_priority::min}, {"slot_exec#" + cell_id_str, task_priority::max, true, is_blocking_mode_active}}, os_thread_realtime_priority::max() - 2, - calculate_affinity_mask("du_cell#" + cell_id_str, os_thread_realtime_priority::max() - 2)}; + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::l2_cell)}; if (not exec_mng.add_execution_context(create_execution_context(du_cell_worker))) { report_fatal_error("Failed to instantiate {} execution context", du_cell_worker.name); @@ -183,7 +199,9 @@ void worker_manager::create_du_cu_executors(bool is_blocki using exec_list = std::initializer_list; auto cell_exec_mapper = std::make_unique(exec_list{exec_map.at("cell_exec#" + cell_id_str)}, exec_list{exec_map.at("slot_exec#" + cell_id_str)}); - auto ue_exec_mapper = std::make_unique(exec_list{exec_map.at("du_ue_exec")}); + auto ue_exec_mapper = std::make_unique(exec_list{exec_map.at("ue_up_ctrl_exec")}, + exec_list{exec_map.at("ue_ul_exec")}, + exec_list{exec_map.at("ue_dl_exec")}); du_item.du_high_exec_mapper = std::make_unique(std::move(cell_exec_mapper), std::move(ue_exec_mapper), *exec_map.at("du_ctrl_exec"), @@ -191,14 +209,28 @@ void worker_manager::create_du_cu_executors(bool is_blocki *exec_map.at("du_e2_exec")); } - create_du_low_executors( - is_blocking_mode_active, nof_ul_workers, nof_dl_workers, nof_pdsch_workers, cells_cfg, pipeline_depth); + // Select the PDSCH concurrent thread only if the PDSCH processor type is set to concurrent or auto. + unsigned nof_pdsch_workers = 1; + const upper_phy_threads_appconfig& upper_phy_threads_cfg = appcfg.expert_execution_cfg.threads.upper_threads; + if ((upper_phy_threads_cfg.pdsch_processor_type == "concurrent") || + (upper_phy_threads_cfg.pdsch_processor_type == "auto")) { + nof_pdsch_workers = upper_phy_threads_cfg.nof_pdsch_threads; + } + + create_du_low_executors(is_blocking_mode_active, + upper_phy_threads_cfg.nof_ul_threads, + upper_phy_threads_cfg.nof_dl_threads, + nof_pdsch_workers, + upper_phy_threads_cfg.nof_pusch_decoder_threads, + cells_cfg, + appcfg.expert_phy_cfg.max_processing_delay_slots); } void worker_manager::create_du_low_executors(bool is_blocking_mode_active, unsigned nof_ul_workers, unsigned nof_dl_workers, unsigned nof_pdsch_workers, + unsigned nof_pusch_decoder_workers, span cells_cfg, unsigned pipeline_depth) { @@ -209,7 +241,11 @@ void worker_manager::create_du_low_executors(bool is_block if (is_blocking_mode_active) { // Create a single worker, shared by the whole PHY. - create_prio_worker("phy_worker", task_worker_queue_size, {{"phy_exec"}}, os_thread_realtime_priority::max()); + create_prio_worker("phy_worker", + task_worker_queue_size, + {{"phy_exec"}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::l1_dl), + os_thread_realtime_priority::max()); for (unsigned cell_id = 0, cell_end = cells_cfg.size(); cell_id != cell_end; ++cell_id) { upper_pusch_exec.push_back(exec_mng.executors().at("phy_exec")); @@ -223,12 +259,10 @@ void worker_manager::create_du_low_executors(bool is_block for (unsigned cell_id = 0, cell_end = cells_cfg.size(); cell_id != cell_end; ++cell_id) { const std::string cell_id_str = std::to_string(cell_id); const std::string name_ul = "up_phy_ul#" + cell_id_str; - const auto prio = os_thread_realtime_priority::max() - 20; + const auto prio = os_thread_realtime_priority::max() - 15; std::vector cpu_masks; - if (use_tuned_profile) { - for (unsigned w = 0; w != nof_ul_workers; ++w) { - cpu_masks.push_back(affinity_manager->reserve_cpu(name_ul, prio)); - } + for (unsigned w = 0; w != nof_ul_workers; ++w) { + cpu_masks.push_back(affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::l1_ul)); } // Instantiate PHY UL workers. @@ -244,7 +278,11 @@ void worker_manager::create_du_low_executors(bool is_block // Instantiate dedicated PRACH worker. const std::string name_prach = "phy_prach#" + cell_id_str; const std::string prach_exec = "prach_exec#" + cell_id_str; - create_prio_worker(name_prach, task_worker_queue_size, {{prach_exec}}, os_thread_realtime_priority::max() - 2); + create_prio_worker(name_prach, + task_worker_queue_size, + {{prach_exec}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::l1_ul), + os_thread_realtime_priority::max() - 2); upper_prach_exec.push_back(exec_mng.executors().at("prach_exec#" + cell_id_str)); // Instantiate dedicated PHY DL workers. @@ -253,12 +291,39 @@ void worker_manager::create_du_low_executors(bool is_block const std::string suffix = std::to_string(cell_id) + "#" + std::to_string(i_dl_worker); const std::string worker_name = "up_phy_dl#" + suffix; const std::string exec_name = "du_low_dl_exec#" + suffix; - create_prio_worker(worker_name, task_worker_queue_size, {{exec_name}}, os_thread_realtime_priority::max() - 10); + create_prio_worker(worker_name, + task_worker_queue_size, + {{exec_name}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::l1_dl), + os_thread_realtime_priority::max() - 10); du_low_dl_executors[cell_id].emplace_back(exec_mng.executors().at("du_low_dl_exec#" + suffix)); } } } + // Instantiate dedicated PUSCH decoder workers for each cell. + for (unsigned cell_id = 0, cell_end = cells_cfg.size(); cell_id != cell_end; ++cell_id) { + if (nof_pusch_decoder_workers > 0) { + const std::string cell_id_str = std::to_string(cell_id); + const std::string name_pusch_decoder = "pusch#" + cell_id_str; + const auto prio = os_thread_realtime_priority::max() - 30; + std::vector cpu_masks; + for (unsigned w = 0; w != nof_pusch_decoder_workers; ++w) { + cpu_masks.push_back(affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::low_priority)); + } + + create_worker_pool(name_pusch_decoder, + nof_pusch_decoder_workers, + task_worker_queue_size, + {{name_pusch_decoder}}, + prio, + cpu_masks); + upper_pusch_decoder_exec.push_back(exec_mng.executors().at(name_pusch_decoder)); + } else { + upper_pusch_decoder_exec.push_back(nullptr); + } + } + for (unsigned cell_id = 0, cell_end = cells_cfg.size(); cell_id != cell_end; ++cell_id) { if (nof_pdsch_workers > 1) { const std::string name_pdsch = "pdsch#" + std::to_string(cell_id); @@ -270,10 +335,8 @@ void worker_manager::create_du_low_executors(bool is_block const auto prio = os_thread_realtime_priority::max() - 10; std::vector cpu_masks; - if (use_tuned_profile) { - for (unsigned w = 0; w != nof_pdsch_workers; ++w) { - cpu_masks.push_back(affinity_manager->reserve_cpu(name_pdsch, prio)); - } + for (unsigned w = 0; w != nof_pdsch_workers; ++w) { + cpu_masks.push_back(affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::l1_dl)); } create_worker_pool(name_pdsch, @@ -287,14 +350,6 @@ void worker_manager::create_du_low_executors(bool is_block } } -/// Returns an affinity bitmask using the given affinity mask manager. -static os_sched_affinity_bitmask -get_affinity_mask(affinity_mask_manager& manager, const std::string& name, unsigned priority_from_max) -{ - const os_thread_realtime_priority priority = os_thread_realtime_priority::max() - priority_from_max; - return manager.reserve_cpu(name, priority); -} - void worker_manager::create_ofh_executors(span cells, bool is_downlink_parallelized) { using namespace execution_config_helper; @@ -322,8 +377,8 @@ void worker_manager::create_ofh_executors(span cells, bool {{exec_name}}, std::chrono::microseconds{0}, os_thread_realtime_priority::max() - 0, - get_affinity_mask(*affinity_manager, name, 0)}; - if (not exec_mng.add_execution_context(create_execution_context(ru_worker))) { + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru)}; + if (!exec_mng.add_execution_context(create_execution_context(ru_worker))) { report_fatal_error("Failed to instantiate {} execution context", ru_worker.name); } ru_timing_exec = exec_mng.executors().at(exec_name); @@ -342,7 +397,7 @@ void worker_manager::create_ofh_executors(span cells, bool {{exec_name}}, nullopt, os_thread_realtime_priority::max() - 5, - get_affinity_mask(*affinity_manager, name, 5)}; + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru)}; if (not exec_mng.add_execution_context(create_execution_context(ru_worker))) { report_fatal_error("Failed to instantiate {} execution context", ru_worker.name); } @@ -359,7 +414,7 @@ void worker_manager::create_ofh_executors(span cells, bool {{exec_name}}, std::chrono::microseconds{5}, os_thread_realtime_priority::max() - 1, - get_affinity_mask(*affinity_manager, name, 1)}; + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru)}; if (not exec_mng.add_execution_context(create_execution_context(ru_worker))) { report_fatal_error("Failed to instantiate {} execution context", ru_worker.name); } @@ -376,7 +431,7 @@ void worker_manager::create_ofh_executors(span cells, bool {{exec_name}}, std::chrono::microseconds{1}, os_thread_realtime_priority::max() - 1, - get_affinity_mask(*affinity_manager, name, 1)}; + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru)}; if (not exec_mng.add_execution_context(create_execution_context(ru_worker))) { report_fatal_error("Failed to instantiate {} execution context", ru_worker.name); } @@ -390,11 +445,17 @@ void worker_manager::create_lower_phy_executors(lower_phy_thread_profile lower_p using namespace execution_config_helper; // Radio Unit worker and executor. - create_prio_worker("radio", task_worker_queue_size, {{"radio_exec"}}); + create_prio_worker("radio", + task_worker_queue_size, + {{"radio_exec"}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru)); radio_exec = exec_mng.executors().at("radio_exec"); // Radio Unit statistics worker and executor. - create_prio_worker("ru_stats_worker", 1, {{"ru_printer_exec"}}); + create_prio_worker("ru_stats_worker", + 1, + {{"ru_printer_exec"}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::low_priority)); ru_printer_exec = exec_mng.executors().at("ru_printer_exec"); for (unsigned cell_id = 0; cell_id != nof_cells; ++cell_id) { @@ -417,7 +478,11 @@ void worker_manager::create_lower_phy_executors(lower_phy_thread_profile lower_p const std::string name = "lower_phy#" + std::to_string(cell_id); const std::string exec_name = "lower_phy_exec#" + std::to_string(cell_id); - create_prio_worker(name, 128, {{exec_name}}, os_thread_realtime_priority::max()); + create_prio_worker(name, + 128, + {{exec_name}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru), + os_thread_realtime_priority::max()); task_executor* phy_exec = exec_mng.executors().at(exec_name); lower_phy_tx_exec.push_back(phy_exec); @@ -435,8 +500,16 @@ void worker_manager::create_lower_phy_executors(lower_phy_thread_profile lower_p const std::string name_ul = "lower_phy_ul#" + std::to_string(cell_id); const std::string exec_ul = "lower_phy_ul_exec#" + std::to_string(cell_id); - create_prio_worker(name_dl, 128, {{exec_dl}}, os_thread_realtime_priority::max()); - create_prio_worker(name_ul, 2, {{exec_ul}}, os_thread_realtime_priority::max() - 1); + create_prio_worker(name_dl, + 128, + {{exec_dl}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru), + os_thread_realtime_priority::max()); + create_prio_worker(name_ul, + 2, + {{exec_ul}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru), + os_thread_realtime_priority::max() - 1); lower_phy_tx_exec.push_back(exec_mng.executors().at(exec_dl)); lower_phy_rx_exec.push_back(exec_mng.executors().at(exec_ul)); @@ -457,10 +530,26 @@ void worker_manager::create_lower_phy_executors(lower_phy_thread_profile lower_p const std::string name_rx = "lower_phy_rx#" + std::to_string(cell_id); const std::string exec_rx = "lower_phy_rx_exec#" + std::to_string(cell_id); - create_prio_worker(name_tx, 128, {{exec_tx}}, os_thread_realtime_priority::max()); - create_prio_worker(name_rx, 1, {{exec_rx}}, os_thread_realtime_priority::max() - 2); - create_prio_worker(name_dl, 128, {{exec_dl}}, os_thread_realtime_priority::max() - 1); - create_prio_worker(name_ul, 128, {{exec_ul}}, os_thread_realtime_priority::max() - 3); + create_prio_worker(name_tx, + 128, + {{exec_tx}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru), + os_thread_realtime_priority::max()); + create_prio_worker(name_rx, + 1, + {{exec_rx}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru), + os_thread_realtime_priority::max() - 2); + create_prio_worker(name_dl, + 128, + {{exec_dl}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru), + os_thread_realtime_priority::max() - 1); + create_prio_worker(name_ul, + 128, + {{exec_ul}}, + affinity_mng.calcute_affinity_mask(gnb_sched_affinity_mask_types::ru), + os_thread_realtime_priority::max() - 3); lower_phy_tx_exec.push_back(exec_mng.executors().at(exec_tx)); lower_phy_rx_exec.push_back(exec_mng.executors().at(exec_rx)); @@ -477,13 +566,15 @@ void worker_manager::create_lower_phy_executors(lower_phy_thread_profile lower_p void worker_manager::create_ru_executors(const gnb_appconfig& appcfg) { if (variant_holds_alternative(appcfg.ru_cfg)) { + create_ofh_executors(appcfg.cells_cfg, appcfg.expert_execution_cfg.threads.ofh_threads.is_downlink_parallelized); + return; } const ru_sdr_appconfig& sdr_cfg = variant_get(appcfg.ru_cfg); std::string driver = sdr_cfg.device_driver; - create_lower_phy_executors((driver != "zmq") ? sdr_cfg.expert_cfg.lphy_executor_profile + create_lower_phy_executors((driver != "zmq") ? appcfg.expert_execution_cfg.threads.lower_threads.execution_profile : lower_phy_thread_profile::blocking, appcfg.cells_cfg.size()); } @@ -504,12 +595,3 @@ void worker_manager::get_du_low_dl_executors(std::vector& execut executors[i_exec] = du_low_exec[i_exec]; } } - -os_sched_affinity_bitmask worker_manager::calculate_affinity_mask(const std::string& worker_name, - os_thread_realtime_priority prio) -{ - if (use_tuned_profile) { - return affinity_manager->reserve_cpu(worker_name, prio); - } - return os_sched_affinity_bitmask{}; -} diff --git a/apps/gnb/gnb_worker_manager.h b/apps/gnb/gnb_worker_manager.h index 6be77e5f06..3e36d13009 100644 --- a/apps/gnb/gnb_worker_manager.h +++ b/apps/gnb/gnb_worker_manager.h @@ -23,6 +23,7 @@ #pragma once #include "gnb_appconfig.h" +#include "gnb_os_sched_affinity_manager.h" #include "srsran/adt/expected.h" #include "srsran/du_high/du_high_executor_mapper.h" #include "srsran/support/executors/task_execution_manager.h" @@ -31,72 +32,6 @@ namespace srsran { -/// \brief Affinity mask manager. -/// -/// It manages the CPUs that have been used to set an affinity mask accounting for a task priority. -class affinity_mask_manager -{ -public: - /// Creates the tuned affinity mask manager with the given number of threads per core, reserves given amount of CPU - /// cores for non priority tasks. - explicit affinity_mask_manager(unsigned nof_threads_per_core_, unsigned nof_cores_for_non_prio_threads_) : - nof_threads_per_core(nof_threads_per_core_), - nof_cores_for_non_prio_threads(nof_cores_for_non_prio_threads_), - cpu_bitset(compute_host_nof_hardware_threads()) - { - srsran_assert( - nof_cores_for_non_prio_threads < compute_host_nof_hardware_threads(), - "Number of CPU cores reserved for non-priority tasks cannot exceed number of CPU cores in the machine"); - - for (unsigned bit = 0; bit != nof_cores_for_non_prio_threads; ++bit) { - non_prio_thread_mask.set(bit); - } - cpu_bitset.fill(0, nof_cores_for_non_prio_threads, true); - } - - /// Default constructor. - affinity_mask_manager() : - nof_threads_per_core(2U), - nof_cores_for_non_prio_threads(compute_host_nof_hardware_threads() / 2), - cpu_bitset(compute_host_nof_hardware_threads()) - { - cpu_bitset.fill(0, nof_cores_for_non_prio_threads, true); - } - - /// \brief Returns an affinity mask with assigned CPU indexes. - /// - /// \param name Name of the task trying to reserve a CPU core. - /// \param prio Priority of a task trying to reserve a CPU core. - /// \return CPU affinity mask. - os_sched_affinity_bitmask reserve_cpu(const std::string& name, - os_thread_realtime_priority prio = os_thread_realtime_priority::no_realtime()) - { - if (prio == os_thread_realtime_priority::no_realtime()) { - return non_prio_thread_mask; - } - int start_pos = std::min(size_t(cpu_bitset.find_highest()) + nof_threads_per_core, cpu_bitset.size() - 1); - int pos = cpu_bitset.find_lowest(start_pos, cpu_bitset.size(), false); - - if (pos == -1) { - fmt::print("Could not set the affinity for the {} worker\n", name); - return {}; - } - - cpu_bitset.set(pos); - return os_sched_affinity_bitmask(pos); - } - -private: - /// Number of threads per physical CPU. - const unsigned nof_threads_per_core; - /// Number of CPU cores assigned for non-priority tasks. - const unsigned nof_cores_for_non_prio_threads; - /// Bitmask used to keep track of reserved CPUs. - bounded_bitset<1024> cpu_bitset; - /// Affinity mask assigned for non-priority tasks. - os_sched_affinity_bitmask non_prio_thread_mask; -}; - /// Manages the workers of the app. struct worker_manager { explicit worker_manager(const gnb_appconfig& appcfg); @@ -123,6 +58,7 @@ struct worker_manager { std::vector lower_phy_ul_exec; std::vector lower_prach_exec; std::vector upper_pusch_exec; + std::vector upper_pusch_decoder_exec; std::vector upper_pucch_exec; std::vector upper_prach_exec; std::vector upper_pdsch_exec; @@ -145,20 +81,20 @@ struct worker_manager { struct du_high_executor_storage { std::unique_ptr du_high_exec_mapper; }; - - std::unique_ptr affinity_manager; - bool use_tuned_profile = false; - std::vector du_high_executors; std::vector> du_low_dl_executors; /// Manager of execution contexts and respective executors instantiated by the gNB application. task_execution_manager exec_mng; + /// CPU affinity bitmask manager. + gnb_os_sched_affinity_manager affinity_mng; + /// Helper method to create workers with non zero priority. void create_prio_worker(const std::string& name, unsigned queue_size, const std::vector& execs, + const os_sched_affinity_bitmask& mask, os_thread_realtime_priority prio = os_thread_realtime_priority::no_realtime()); /// Helper method to create worker pool. @@ -170,17 +106,14 @@ struct worker_manager { span cpu_masks = {}); /// Helper method that creates the Control and Distributed Units executors. - void create_du_cu_executors(bool is_blocking_mode_active, - unsigned nof_ul_workers, - unsigned nof_dl_workers, - unsigned nof_pdsch_workers, - span cells_cfg, - unsigned pipeline_depth); + void create_du_cu_executors(const gnb_appconfig& appcfg); + /// Helper method that creates the low Distributed Unit executors. void create_du_low_executors(bool is_blocking_mode_active, unsigned nof_ul_workers, unsigned nof_dl_workers, unsigned nof_pdsch_workers, + unsigned nof_pusch_decoder_workers, span cells_cfg, unsigned pipeline_depth); @@ -192,9 +125,6 @@ struct worker_manager { /// Helper method that creates the Open Fronthaul executors. void create_ofh_executors(span cells, bool is_downlink_parallelized); - - /// Assign a CPU affinity bitmask to a given worker, based on its priority. - os_sched_affinity_bitmask calculate_affinity_mask(const std::string& worker_name, os_thread_realtime_priority prio); }; } // namespace srsran diff --git a/apps/gnb/helpers/gnb_console_helper.cpp b/apps/gnb/helpers/gnb_console_helper.cpp index 93113a3098..386bea5edb 100644 --- a/apps/gnb/helpers/gnb_console_helper.cpp +++ b/apps/gnb/helpers/gnb_console_helper.cpp @@ -33,8 +33,8 @@ using namespace srsran; -gnb_console_helper::gnb_console_helper(io_broker& io_broker_) : - logger(srslog::fetch_basic_logger("GNB")), io_broker_handle(io_broker_) +gnb_console_helper::gnb_console_helper(io_broker& io_broker_, srslog::log_channel& log_chan_) : + logger(srslog::fetch_basic_logger("GNB")), io_broker_handle(io_broker_), metrics_json(log_chan_) { // set STDIN file descripter into non-blocking mode int flags = fcntl(STDIN_FILENO, F_GETFL, 0); @@ -143,4 +143,4 @@ void gnb_console_helper::on_app_running() void gnb_console_helper::on_app_stopping() { fmt::print("Stopping ..\n"); -} \ No newline at end of file +} diff --git a/apps/gnb/helpers/gnb_console_helper.h b/apps/gnb/helpers/gnb_console_helper.h index b4e17f5e02..a8868a8c12 100644 --- a/apps/gnb/helpers/gnb_console_helper.h +++ b/apps/gnb/helpers/gnb_console_helper.h @@ -22,6 +22,7 @@ #pragma once +#include "metrics_plotter_json.h" #include "metrics_plotter_stdout.h" #include "srsran/du/du_cell_config.h" #include "srsran/scheduler/scheduler_metrics.h" @@ -47,10 +48,11 @@ class app_state_notifier class gnb_console_helper : public app_state_notifier { public: - gnb_console_helper(io_broker& io_broker_); + gnb_console_helper(io_broker& io_broker_, srslog::log_channel& log_chan_); ~gnb_console_helper(); - scheduler_ue_metrics_notifier& get_metrics_notifier() { return metrics_plotter; }; + scheduler_ue_metrics_notifier& get_stdout_metrics_notifier() { return metrics_plotter; }; + scheduler_ue_metrics_notifier& get_json_metrics_notifier() { return metrics_json; }; void on_app_starting() override; void on_app_running() override; @@ -66,6 +68,7 @@ class gnb_console_helper : public app_state_notifier srslog::basic_logger& logger; io_broker& io_broker_handle; metrics_plotter_stdout metrics_plotter; + metrics_plotter_json metrics_json; std::vector cells; }; diff --git a/apps/gnb/helpers/metrics_plotter_json.cpp b/apps/gnb/helpers/metrics_plotter_json.cpp new file mode 100644 index 0000000000..1e5c508b92 --- /dev/null +++ b/apps/gnb/helpers/metrics_plotter_json.cpp @@ -0,0 +1,112 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "metrics_plotter_json.h" +#include "srsran/support/math_utils.h" + +using namespace srsran; + +namespace { + +/// UE container metrics. +DECLARE_METRIC("pci", metric_pci, pci_t, ""); +DECLARE_METRIC("rnti", metric_rnti, uint16_t, ""); +DECLARE_METRIC("cqi", metric_cqi, uint8_t, ""); +DECLARE_METRIC("ri", metric_ri, uint8_t, ""); +DECLARE_METRIC("dl_mcs", metric_dl_mcs, uint8_t, ""); +DECLARE_METRIC("dl_brate", metric_dl_brate, double, ""); +DECLARE_METRIC("dl_nof_ok", metric_dl_nof_ok, unsigned, ""); +DECLARE_METRIC("dl_nof_nok", metric_dl_nof_nok, unsigned, ""); +DECLARE_METRIC("dl_bs", metric_dl_bs, unsigned, ""); +DECLARE_METRIC("pusch_snr_db", metric_pusch_snr_db, float, ""); +DECLARE_METRIC("ul_mcs", metric_ul_mcs, uint8_t, ""); +DECLARE_METRIC("ul_brate", metric_ul_brate, double, ""); +DECLARE_METRIC("ul_nof_ok", metric_ul_nof_ok, unsigned, ""); +DECLARE_METRIC("ul_nof_nok", metric_ul_nof_nok, unsigned, ""); +DECLARE_METRIC("bsr", metric_bsr, unsigned, ""); +DECLARE_METRIC_SET("ue_container", + mset_ue_container, + metric_pci, + metric_rnti, + metric_cqi, + metric_ri, + metric_dl_mcs, + metric_dl_brate, + metric_dl_nof_ok, + metric_dl_nof_nok, + metric_dl_bs, + metric_pusch_snr_db, + metric_ul_mcs, + metric_ul_brate, + metric_ul_nof_ok, + metric_ul_nof_nok, + metric_bsr); + +/// Metrics root object. +DECLARE_METRIC("timestamp", metric_timestamp_tag, double, ""); +DECLARE_METRIC_LIST("ue_list", mlist_ues, std::vector); + +/// Metrics context. +using metric_context_t = srslog::build_context_type; + +} // namespace + +/// Returns the current time in seconds with ms precision since UNIX epoch. +static double get_time_stamp() +{ + auto tp = std::chrono::system_clock::now().time_since_epoch(); + return std::chrono::duration_cast(tp).count() * 1e-3; +} + +void metrics_plotter_json::report_metrics(span ue_metrics) +{ + metric_context_t ctx("JSON Metrics"); + + for (const auto& ue : ue_metrics) { + ctx.get().emplace_back(); + auto& output = ctx.get().back(); + + output.write(ue.pci); + output.write(ue.rnti); + if (ue.cqi) { + output.write(ue.cqi); + } + output.write(ue.ri); + output.write(ue.dl_mcs.to_uint()); + output.write(ue.dl_brate_kbps * 1e3); + output.write(ue.dl_nof_ok); + output.write(ue.dl_nof_nok); + output.write(ue.dl_bs); + if (!std::isnan(ue.pusch_snr_db) && !iszero(ue.pusch_snr_db)) { + output.write(clamp(ue.pusch_snr_db, -99.9f, 99.9f)); + } + output.write(ue.ul_mcs.to_uint()); + output.write(ue.ul_brate_kbps * 1e3); + output.write(ue.ul_nof_ok); + output.write(ue.ul_nof_nok); + output.write(ue.bsr); + } + + // Log the context. + ctx.write(get_time_stamp()); + log_chan(ctx); +} diff --git a/apps/gnb/helpers/metrics_plotter_json.h b/apps/gnb/helpers/metrics_plotter_json.h new file mode 100644 index 0000000000..d1fc697b64 --- /dev/null +++ b/apps/gnb/helpers/metrics_plotter_json.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/scheduler/scheduler_metrics.h" + +namespace srsran { + +/// Class used to receive metrics reports from scheduler and format them into a JSON file. +class metrics_plotter_json : public scheduler_ue_metrics_notifier +{ +public: + explicit metrics_plotter_json(srslog::log_channel& log_chan_) : log_chan(log_chan_) {} + + /// Notifier called from the scheduler. + void report_metrics(span ue_metrics) override; + +private: + srslog::log_channel& log_chan; +}; + +} // namespace srsran diff --git a/apps/gnb/helpers/metrics_plotter_stdout.cpp b/apps/gnb/helpers/metrics_plotter_stdout.cpp index d0200001c8..4dd816004b 100644 --- a/apps/gnb/helpers/metrics_plotter_stdout.cpp +++ b/apps/gnb/helpers/metrics_plotter_stdout.cpp @@ -22,18 +22,18 @@ #include "metrics_plotter_stdout.h" #include "srsran/support/math_utils.h" -#include +#include #include -#include +#include using namespace srsran; static std::string scaled_fmt_integer(uint64_t num) { - constexpr static std::array suffixes = {"", "k", "M", "G", "T", "P", "E", "Z"}; - const static std::array max_nums = []() { + static constexpr std::array suffixes = {"", "k", "M", "G", "T", "P", "E", "Z"}; + static const std::array max_nums = []() { std::array nums{0}; - for (unsigned i = 0; i < nums.size(); ++i) { + for (unsigned i = 0, e = nums.size(); i != e; ++i) { nums[i] = (uint64_t)std::pow(10, i * 3); } return nums; @@ -43,28 +43,93 @@ static std::string scaled_fmt_integer(uint64_t num) return fmt::format("{:>6}", num); } - for (unsigned i = 1; i != max_nums.size() - 1; ++i) { + for (unsigned i = 1, e = max_nums.size() - 1; i != e; ++i) { if (num < max_nums[i + 1]) { return fmt::format("{:>5.3g}{}", num / static_cast(max_nums[i]), suffixes[i]); } } - return std::string("Invalid number"); + + return "Invalid number"; } -void metrics_plotter_stdout::print_header() +static void print_header() { fmt::print("\n"); fmt::print(" -----------------DL-----------------------|------------------UL--------------------\n"); fmt::print(" pci rnti cqi ri mcs brate ok nok (%) dl_bs | pusch mcs brate ok nok (%) bsr\n"); } +static std::string float_to_string(float f, int digits, int field_width) +{ + std::ostringstream os; + int precision; + + if (std::isnan(f) || std::abs(f) < 0.0001f) { + f = 0.f; + precision = digits - 1; + } else { + precision = digits - (int)(std::log10(std::abs(f + 0.0001f)) - 2 * DBL_EPSILON); + } + + precision = std::max(precision, 0); + + os << std::setw(field_width) << std::fixed << std::setprecision(precision) << f; + return os.str(); +} + +static std::string float_to_eng_string(float f, int digits) +{ + static char const* const prefixes[2][9] = { + { + "", + "m", + "u", + "n", + "p", + "f", + "a", + "z", + "y", + }, + { + "", + "k", + "M", + "G", + "T", + "P", + "E", + "Z", + "Y", + }, + }; + + const int degree = (f == 0.f) ? 0 : std::lrint(std::floor(std::log10(std::abs(f)) / 3)); + + std::string factor; + + if (std::abs(degree) < 9) { + factor = prefixes[(degree < 0) ? 0 : 1][std::abs(degree)]; + } else { + return "failed"; + } + + const double scaled = f * std::pow(1000.0, -degree); + if (degree != 0) { + return float_to_string(scaled, digits, 5) + factor; + } + return " " + float_to_string(scaled, digits, 5 - factor.length()) + factor; +} + void metrics_plotter_stdout::report_metrics(span ue_metrics) { - if (not print_metrics) { + if (!print_metrics) { return; } - if (++nof_lines > 10 && !ue_metrics.empty()) { + if (ue_metrics.size() > 10) { + print_header(); + } else if (++nof_lines > 10 && !ue_metrics.empty()) { nof_lines = 0; print_header(); } @@ -72,7 +137,7 @@ void metrics_plotter_stdout::report_metrics(span ue_ for (const auto& ue : ue_metrics) { fmt::print("{:>4}", ue.pci); fmt::print("{:>5x}", ue.rnti); - if (not iszero(ue.cqi)) { + if (!iszero(ue.cqi)) { fmt::print(" {:>3}", int(ue.cqi)); } else { fmt::print(" {:>3.3}", "n/a"); @@ -80,11 +145,7 @@ void metrics_plotter_stdout::report_metrics(span ue_ fmt::print(" {:>2}", int(ue.ri)); - if (not std::isnan(ue.dl_mcs.to_uint())) { - fmt::print(" {:>2}", int(ue.dl_mcs.to_uint())); - } else { - fmt::print(" {:>2}", 0); - } + fmt::print(" {:>2}", int(ue.dl_mcs.to_uint())); if (ue.dl_brate_kbps > 0) { fmt::print(" {:>6.6}", float_to_eng_string(ue.dl_brate_kbps * 1e3, 1)); } else { @@ -102,17 +163,13 @@ void metrics_plotter_stdout::report_metrics(span ue_ fmt::print(" |"); - if (not std::isnan(ue.pusch_snr_db) and not iszero(ue.pusch_snr_db)) { + if (!std::isnan(ue.pusch_snr_db) && !iszero(ue.pusch_snr_db)) { fmt::print(" {:>5.1f}", clamp(ue.pusch_snr_db, -99.9f, 99.9f)); } else { fmt::print(" {:>5.5}", "n/a"); } - if (not std::isnan(ue.ul_mcs.to_uint())) { - fmt::print(" {:>2}", ue.ul_mcs.to_uint()); - } else { - fmt::print(" {:>2}", 0); - } + fmt::print(" {:>2}", ue.ul_mcs.to_uint()); if (ue.ul_brate_kbps > 0) { fmt::print(" {:>6.6}", float_to_eng_string(ue.ul_brate_kbps * 1e3, 1)); } else { @@ -137,68 +194,3 @@ void metrics_plotter_stdout::toggle_print() { print_metrics = !print_metrics; } - -static char const* const prefixes[2][9] = { - { - "", - "m", - "u", - "n", - "p", - "f", - "a", - "z", - "y", - }, - { - "", - "k", - "M", - "G", - "T", - "P", - "E", - "Z", - "Y", - }, -}; - -std::string metrics_plotter_stdout::float_to_string(float f, int digits, int field_width) -{ - std::ostringstream os; - int precision; - if (std::isnan(f) or std::fabs(f) < 0.0001) { - f = 0.0; - precision = digits - 1; - } else { - precision = digits - (int)(log10f(fabs(f + 0.0001)) - 2 * DBL_EPSILON); - } - if (precision == -1) { - precision = 0; - } - os << std::setw(field_width) << std::fixed << std::setprecision(precision) << f; - return os.str(); -} - -std::string metrics_plotter_stdout::float_to_eng_string(float f, int digits) -{ - const int degree = (f == 0.0) ? 0 : lrint(floor(log10f(fabs(f)) / 3)); - - std::string factor; - - if (abs(degree) < 9) { - if (degree < 0) - factor = prefixes[0][abs(degree)]; - else - factor = prefixes[1][abs(degree)]; - } else { - return "failed"; - } - - const double scaled = f * pow(1000.0, -degree); - if (degree != 0) { - return float_to_string(scaled, digits, 5) + factor; - } else { - return " " + float_to_string(scaled, digits, 5 - factor.length()) + factor; - } -} \ No newline at end of file diff --git a/apps/gnb/helpers/metrics_plotter_stdout.h b/apps/gnb/helpers/metrics_plotter_stdout.h index b466bc5a72..e58dc2ff6f 100644 --- a/apps/gnb/helpers/metrics_plotter_stdout.h +++ b/apps/gnb/helpers/metrics_plotter_stdout.h @@ -26,23 +26,20 @@ namespace srsran { -/// \brief Class used to receive metrics reports from scheduler and pretty-print them to the console. +/// Class used to receive metrics reports from scheduler and pretty-print them to the console. class metrics_plotter_stdout : public scheduler_ue_metrics_notifier { public: metrics_plotter_stdout() = default; - /// \brief Notifier called from the scheduler. + /// Notifier called from the scheduler. void report_metrics(span ue_metrics) override; - /// \brief This can be called from another execution context to turn on/off the actual plotting. + /// This can be called from another execution context to turn on/off the actual plotting. void toggle_print(); private: - std::string float_to_string(float f, int digits, int field_width); - std::string float_to_eng_string(float f, int digits); - void print_header(); - int nof_lines = 10; + unsigned nof_lines = 10; std::atomic print_metrics = {false}; }; diff --git a/configs/gnb_ru_rpqn4800e_tdd_n78_20mhz.yml b/configs/gnb_ru_rpqn4800e_tdd_n78_20mhz.yml index a882af8569..638ca89b42 100644 --- a/configs/gnb_ru_rpqn4800e_tdd_n78_20mhz.yml +++ b/configs/gnb_ru_rpqn4800e_tdd_n78_20mhz.yml @@ -15,8 +15,8 @@ ru_ofh: t1a_min_cp_ul: 250 # Minimum T1a on Control-Plane for Uplink in microseconds. t1a_max_up: 250 # Maximum T1a on User-Plane in microseconds. t1a_min_up: 80 # Minimum T1a on User-Plane in microseconds. - ta4_max: 150 # Maximum Ta4 on User-Plane in microseconds. - ta4_min: 25 # Minimum Ta4 on User-Plane in microseconds. + ta4_max: 200 # Maximum Ta4 on User-Plane in microseconds. + ta4_min: 0 # Minimum Ta4 on User-Plane in microseconds. is_prach_cp_enabled: true # Configures if Control-Plane messages should be used to receive PRACH messages. ignore_ecpri_payload_size: true # Configures if eCPRI payload size field should be ignored by the eCPRI packet decoder. compr_method_ul: bfp # Uplink compression method. diff --git a/docker/README.md b/docker/README.md index da94b86b42..48dc77105a 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,4 +1,4 @@ -# SRSRAN Project Docker +# srsRAN Project Docker To build and launch the gnb, please run: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 15bdf1df3f..7152f1f938 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -27,7 +27,7 @@ services: context: open5gs target: open5gs args: - OS_VERSION: "20.04" + OS_VERSION: "22.04" OPEN5GS_VERSION: "v2.6.1" environment: MONGODB_IP: ${MONGODB_IP:-127.0.0.1} diff --git a/docker/open5gs/Dockerfile b/docker/open5gs/Dockerfile index b0059b2431..ba2c1e93f4 100644 --- a/docker/open5gs/Dockerfile +++ b/docker/open5gs/Dockerfile @@ -4,11 +4,11 @@ FROM ubuntu:$OS_VERSION as base ENV PYTHONBUFFERED=1 ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update \ +RUN DEBIAN_FRONTEND=noninteractive apt-get update \ && apt install -y software-properties-common \ && rm -rf /var/lib/apt/lists/* -RUN apt-get update \ +RUN DEBIAN_FRONTEND=noninteractive apt-get update \ && apt-get install -y \ python3-pip \ python3-setuptools \ @@ -32,7 +32,6 @@ RUN apt-get update \ libnghttp2-dev \ libtins-dev \ meson \ - mongodb \ curl \ gettext \ gdb \ @@ -43,10 +42,19 @@ RUN apt-get update \ iperf \ iperf3 \ libtalloc-dev \ - cmake + cmake \ + && rm -rf /var/lib/apt/lists/* + +ARG MONGO_MAJOR_VERSION=6 +RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y --no-install-recommends wget gnupg \ + && wget -qO - https://www.mongodb.org/static/pgp/server-${MONGO_MAJOR_VERSION}.0.asc | apt-key add \ + && . /etc/os-release \ + && echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu $UBUNTU_CODENAME/mongodb-org/${MONGO_MAJOR_VERSION}.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-${MONGO_MAJOR_VERSION}.0.list \ + && DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y --no-install-recommends mongodb-org \ + && apt-get autoremove && apt-get clean # To set a fixed version of open5gs use -ARG OPEN5GS_VERSION +ARG OPEN5GS_VERSION=v2.6.1 RUN echo $OPEN5GS_VERSION > ./open5gsversion # get latest open5gs tag (must be stored in a file, because docker does not allow to use the return value directly) # RUN git ls-remote --tags https://github.com/open5gs/open5gs | sort -t '/' -k 3 -V | awk -F/ '{ print $3 }' | awk '!/\^\{\}/' | tail -n 1 > ./open5gsversion diff --git a/docker/open5gs/README.md b/docker/open5gs/README.md index 7c972684fc..90a5fd4ee6 100644 --- a/docker/open5gs/README.md +++ b/docker/open5gs/README.md @@ -1,16 +1,23 @@ -This is a all-in-one Docker container for Open5GS. At build, the container will use the latest tag of the open5gs repository (). To run a specific version of open5gs, line 48 in .Dockerfile (`# RUN echo "v2.4.3" > ./open5gsversion)` must be uncommented. +This is a all-in-one Docker container for Open5GS. At build, the container will use the specified version of the open5gs repository (default v2.6.1 +). To run the latest tag of the open5gs repository (), line 51 and 52 in .Dockerfile +``` +# get latest open5gs tag (must be stored in a file, because docker does not allow to use the return value directly) +# RUN git ls-remote --tags https://github.com/open5gs/open5gs | sort -t '/' -k 3 -V | awk -F/ '{ print $3 }' | awk '!/\^\{\}/' | tail -n 1 > ./open5gsversion +``` +must be uncommented. + # Container Parameters In [open5gs.env](open5gs.env) the following parameters can be set: -- `MONGODB_IP` (default: 127.0.0.1): This is the IP of the mongodb to use. 127.0.0.1 is the mongodb that runs inside this container. -- `OPEN5GS_IP`: This must be set to the IP of the container (here: 10.53.1.2). -- `UE_IP_BASE`: Defines the IP base used for connected UEs (here: 10.45.0). -- `DEBUG` (default: false): This can be set to true to run Open5GS in debug mode. -- `SUBSCRIBER_DB` (default: "001010123456780,00112233445566778899aabbccddeeff,opc,63bfa50ee6523365ff14c1f45f88737d,8000,9,10.45.1.2") contains either: +- MONGODB_IP (default: 127.0.0.1): This is the IP of the mongodb to use. 127.0.0.1 is the mongodb that runs inside this container. +- SUBSCRIBER_DB (default: "001010123456780,00112233445566778899aabbccddeeff,opc,63bfa50ee6523365ff14c1f45f88737d,8000,10.45.1.2"): This adds subscriber data for a single or multiple users to the Open5GS mongodb. It contains either: - Comma separated string with information to define a subscriber - - A path to a csv file that contains entries to add to open5gs mongodb. Each entry will represent a subscriber. + - `subscriber_db.csv`. This is a csv file that contains entries to add to open5gs mongodb. Each entry will represent a subscriber. It must be stored in `srsgnb/docker/open5gs/` +- OPEN5GS_IP: This must be set to the IP of the container (here: 10.53.1.2). +- UE_IP_BASE: Defines the IP base used for connected UEs (here: 10.45.0). +- DEBUG (default: false): This can be set to true to run Open5GS in debug mode. ``` # Kept in the following format: "Name,IMSI,Key,OP_Type,OP/OPc,AMF,QCI,IP_alloc" @@ -47,6 +54,8 @@ Build the Docker container using: `docker build --target open5gs -t open5gs-docker .` +You can overwrite open5gs version by adding `--build-arg OPEN5GS_VERSION=v2.6.1` + Then run the docker container with: `docker run --net open5gsnet --ip 10.53.1.2 --env-file open5gs.env --privileged --publish 3000:3000 open5gs-docker ./build/tests/app/5gc -c open5gs-5gc.yml` diff --git a/docker/open5gs/subscriber_db.csv.example b/docker/open5gs/subscriber_db.csv.example index 4dcc2198c9..e9a82814c6 100644 --- a/docker/open5gs/subscriber_db.csv.example +++ b/docker/open5gs/subscriber_db.csv.example @@ -9,9 +9,7 @@ # OP/OPc: Operator Code/Cyphered Operator Code, stored in hexadecimal # AMF: Authentication management field, stored in hexadecimal # QCI: QoS Class Identifier for the UE's default bearer. -# IP_alloc: IP allocation stratagy for the SPGW. -# With 'dynamic' the SPGW will automatically allocate IPs -# With a valid IPv4 (e.g. '10.45.0.2') the UE will have a statically assigned IP. +# IP_alloc: Statically assigned IP for the UE. # # Note: Lines starting by '#' are ignored and will be overwritten # List of UEs with IMSI, and key increasing by one for each new UE. Useful for testing with AmariUE simulator and ue_count option diff --git a/docs/index.html b/docs/index.html index 97a6453e2a..aad6a772f7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -87,51 +87,14 @@

srsRAN Project documentation

Documentation

- Doxygen + Doxygen

Cppcheck Analysis

- Clang Tidy Analysis - -

Code coverage

-

-

- e2e test: -

- - + \ No newline at end of file diff --git a/external/rigtorp/MPMCQueue.h b/external/rigtorp/MPMCQueue.h new file mode 100644 index 0000000000..7b6835e3cf --- /dev/null +++ b/external/rigtorp/MPMCQueue.h @@ -0,0 +1,297 @@ +/* +Copyright (c) 2020 Erik Rigtorp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +#pragma once + +#include +#include +#include // offsetof +#include +#include +#include // std::hardware_destructive_interference_size +#include + +#ifndef __cpp_aligned_new +#ifdef _WIN32 +#include // _aligned_malloc +#else +#include // posix_memalign +#endif +#endif + +namespace rigtorp { +namespace mpmc { +#if defined(__cpp_lib_hardware_interference_size) && !defined(__APPLE__) +static constexpr size_t hardwareInterferenceSize = + std::hardware_destructive_interference_size; +#else +static constexpr size_t hardwareInterferenceSize = 64; +#endif + +#if defined(__cpp_aligned_new) +template using AlignedAllocator = std::allocator; +#else +template struct AlignedAllocator { + using value_type = T; + + T *allocate(std::size_t n) { + if (n > std::numeric_limits::max() / sizeof(T)) { + throw std::bad_array_new_length(); + } +#ifdef _WIN32 + auto *p = static_cast(_aligned_malloc(sizeof(T) * n, alignof(T))); + if (p == nullptr) { + throw std::bad_alloc(); + } +#else + T *p; + if (posix_memalign(reinterpret_cast(&p), alignof(T), + sizeof(T) * n) != 0) { + throw std::bad_alloc(); + } +#endif + return p; + } + + void deallocate(T *p, std::size_t) { +#ifdef _WIN32 + _aligned_free(p); +#else + free(p); +#endif + } +}; +#endif + +template struct Slot { + ~Slot() noexcept { + if (turn & 1) { + destroy(); + } + } + + template void construct(Args &&...args) noexcept { + static_assert(std::is_nothrow_constructible::value, + "T must be nothrow constructible with Args&&..."); + new (&storage) T(std::forward(args)...); + } + + void destroy() noexcept { + static_assert(std::is_nothrow_destructible::value, + "T must be nothrow destructible"); + reinterpret_cast(&storage)->~T(); + } + + T &&move() noexcept { return reinterpret_cast(storage); } + + // Align to avoid false sharing between adjacent slots + alignas(hardwareInterferenceSize) std::atomic turn = {0}; + typename std::aligned_storage::type storage; +}; + +template >> +class Queue { +private: + static_assert(std::is_nothrow_copy_assignable::value || + std::is_nothrow_move_assignable::value, + "T must be nothrow copy or move assignable"); + + static_assert(std::is_nothrow_destructible::value, + "T must be nothrow destructible"); + +public: + explicit Queue(const size_t capacity, + const Allocator &allocator = Allocator()) + : capacity_(capacity), allocator_(allocator), head_(0), tail_(0) { + if (capacity_ < 1) { + throw std::invalid_argument("capacity < 1"); + } + // Allocate one extra slot to prevent false sharing on the last slot + slots_ = allocator_.allocate(capacity_ + 1); + // Allocators are not required to honor alignment for over-aligned types + // (see http://eel.is/c++draft/allocator.requirements#10) so we verify + // alignment here + if (reinterpret_cast(slots_) % alignof(Slot) != 0) { + allocator_.deallocate(slots_, capacity_ + 1); + throw std::bad_alloc(); + } + for (size_t i = 0; i < capacity_; ++i) { + new (&slots_[i]) Slot(); + } + static_assert( + alignof(Slot) == hardwareInterferenceSize, + "Slot must be aligned to cache line boundary to prevent false sharing"); + static_assert(sizeof(Slot) % hardwareInterferenceSize == 0, + "Slot size must be a multiple of cache line size to prevent " + "false sharing between adjacent slots"); + static_assert(sizeof(Queue) % hardwareInterferenceSize == 0, + "Queue size must be a multiple of cache line size to " + "prevent false sharing between adjacent queues"); + static_assert( + offsetof(Queue, tail_) - offsetof(Queue, head_) == + static_cast(hardwareInterferenceSize), + "head and tail must be a cache line apart to prevent false sharing"); + } + + ~Queue() noexcept { + for (size_t i = 0; i < capacity_; ++i) { + slots_[i].~Slot(); + } + allocator_.deallocate(slots_, capacity_ + 1); + } + + // non-copyable and non-movable + Queue(const Queue &) = delete; + Queue &operator=(const Queue &) = delete; + + template void emplace(Args &&...args) noexcept { + static_assert(std::is_nothrow_constructible::value, + "T must be nothrow constructible with Args&&..."); + auto const head = head_.fetch_add(1); + auto &slot = slots_[idx(head)]; + while (turn(head) * 2 != slot.turn.load(std::memory_order_acquire)) + ; + slot.construct(std::forward(args)...); + slot.turn.store(turn(head) * 2 + 1, std::memory_order_release); + } + + template bool try_emplace(Args &&...args) noexcept { + static_assert(std::is_nothrow_constructible::value, + "T must be nothrow constructible with Args&&..."); + auto head = head_.load(std::memory_order_acquire); + for (;;) { + auto &slot = slots_[idx(head)]; + if (turn(head) * 2 == slot.turn.load(std::memory_order_acquire)) { + if (head_.compare_exchange_strong(head, head + 1)) { + slot.construct(std::forward(args)...); + slot.turn.store(turn(head) * 2 + 1, std::memory_order_release); + return true; + } + } else { + auto const prevHead = head; + head = head_.load(std::memory_order_acquire); + if (head == prevHead) { + return false; + } + } + } + } + + void push(const T &v) noexcept { + static_assert(std::is_nothrow_copy_constructible::value, + "T must be nothrow copy constructible"); + emplace(v); + } + + template ::value>::type> + void push(P &&v) noexcept { + emplace(std::forward

(v)); + } + + bool try_push(const T &v) noexcept { + static_assert(std::is_nothrow_copy_constructible::value, + "T must be nothrow copy constructible"); + return try_emplace(v); + } + + template ::value>::type> + bool try_push(P &&v) noexcept { + return try_emplace(std::forward

(v)); + } + + void pop(T &v) noexcept { + auto const tail = tail_.fetch_add(1); + auto &slot = slots_[idx(tail)]; + while (turn(tail) * 2 + 1 != slot.turn.load(std::memory_order_acquire)) + ; + v = slot.move(); + slot.destroy(); + slot.turn.store(turn(tail) * 2 + 2, std::memory_order_release); + } + + bool try_pop(T &v) noexcept { + auto tail = tail_.load(std::memory_order_acquire); + for (;;) { + auto &slot = slots_[idx(tail)]; + if (turn(tail) * 2 + 1 == slot.turn.load(std::memory_order_acquire)) { + if (tail_.compare_exchange_strong(tail, tail + 1)) { + v = slot.move(); + slot.destroy(); + slot.turn.store(turn(tail) * 2 + 2, std::memory_order_release); + return true; + } + } else { + auto const prevTail = tail; + tail = tail_.load(std::memory_order_acquire); + if (tail == prevTail) { + return false; + } + } + } + } + + /// Returns the number of elements in the queue. + /// The size can be negative when the queue is empty and there is at least one + /// reader waiting. Since this is a concurrent queue the size is only a best + /// effort guess until all reader and writer threads have been joined. + ptrdiff_t size() const noexcept { + // TODO: How can we deal with wrapped queue on 32bit? + return static_cast(head_.load(std::memory_order_relaxed) - + tail_.load(std::memory_order_relaxed)); + } + + /// Returns true if the queue is empty. + /// Since this is a concurrent queue this is only a best effort guess + /// until all reader and writer threads have been joined. + bool empty() const noexcept { return size() <= 0; } + + size_t capacity() const noexcept { return capacity_; } + +private: + constexpr size_t idx(size_t i) const noexcept { return i % capacity_; } + + constexpr size_t turn(size_t i) const noexcept { return i / capacity_; } + +private: + const size_t capacity_; + Slot *slots_; +#if defined(__has_cpp_attribute) && __has_cpp_attribute(no_unique_address) + Allocator allocator_ [[no_unique_address]]; +#else + Allocator allocator_; +#endif + + // Align to avoid false sharing between head_ and tail_ + alignas(hardwareInterferenceSize) std::atomic head_; + alignas(hardwareInterferenceSize) std::atomic tail_; +}; +} // namespace mpmc + +template >> +using MPMCQueue = mpmc::Queue; + +} // namespace rigtorp diff --git a/include/srsran/adt/bit_buffer.h b/include/srsran/adt/bit_buffer.h index b5a40a237b..5c3a6fc84c 100644 --- a/include/srsran/adt/bit_buffer.h +++ b/include/srsran/adt/bit_buffer.h @@ -65,6 +65,9 @@ class bit_buffer } public: + /// Creates a bit buffer from a view of bytes. + static bit_buffer from_bytes(span bytes) { return bit_buffer(bytes, bytes.size() * bits_per_word); } + /// Fill with zeros. void zero() { std::fill_n(buffer.begin(), nof_words(), 0); } @@ -216,6 +219,16 @@ class bit_buffer return bit_buffer(buffer, count); } + /// Creates a read-only bit buffer pointing at the first \c count bits. + const bit_buffer first(unsigned count) const + { + srsran_assert(size() >= count, + "The buffer size (i.e., {}) must be greater than or equal to the number of bits (i.e., {}).", + size(), + count); + return bit_buffer(buffer, count); + } + /// \brief Creates another bit buffer pointing at the last \c count bits. /// \remark An assertion is triggered if the bits are not aligned with a bit word boundary. bit_buffer last(unsigned count) @@ -228,6 +241,17 @@ class bit_buffer return bit_buffer(buffer.subspan(buffer_start, buffer_len), count); } + /// Creates a read-only bit buffer pointing at the first \c count bits. + const bit_buffer last(unsigned count) const + { + srsran_assert((size() - count) % bits_per_word == 0, "Only bit word boundaries are supported."); + + unsigned buffer_start = (size() - count) / bits_per_word; + unsigned buffer_len = divide_ceil(count, bits_per_word); + + return bit_buffer(buffer.subspan(buffer_start, buffer_len), count); + } + /// Gets the current bit buffer size. size_t size() const { return current_size; } @@ -296,10 +320,10 @@ class bit_buffer bool operator!=(const bit_buffer& other) const { return !(*this == other); } /// Gets the storage read-write buffer view for advanced usage. - span get_buffer() { return buffer; } + span get_buffer() { return buffer.first(nof_words()); } /// Gets the storage read-only buffer view for advanced usage. - span get_buffer() const { return buffer; } + span get_buffer() const { return buffer.first(nof_words()); } private: /// Determines the number of words that are currently used. diff --git a/include/srsran/adt/byte_buffer.h b/include/srsran/adt/byte_buffer.h index 515cb0bddb..6cdb735070 100644 --- a/include/srsran/adt/byte_buffer.h +++ b/include/srsran/adt/byte_buffer.h @@ -343,7 +343,7 @@ class byte_buffer void append(const std::initializer_list& bytes) { append(span{bytes.begin(), bytes.size()}); } /// Appends bytes from another byte_buffer. This function may allocate new segments. - bool append(const byte_buffer& other) + SRSRAN_NODISCARD bool append(const byte_buffer& other) { srsran_assert(&other != this, "Self-append not supported"); if (empty() and not other.empty()) { @@ -382,8 +382,7 @@ class byte_buffer } if (not other.ctrl_blk_ptr.unique()) { // Use lvalue append. - append(other); - return true; + return append(other); } // This is the last reference to "after". Shallow copy, except control segment. node_t* node = create_segment(0); @@ -470,8 +469,7 @@ class byte_buffer } if (empty()) { // the byte buffer is empty. Prepending is the same as appending. - append(other); - return true; + return append(other); } for (span seg : other.segments()) { node_t* node = create_segment(0); @@ -1016,7 +1014,7 @@ class byte_buffer_writer void append(span bytes) { buffer->append(bytes); } /// Appends single byte. - void append(uint8_t byte) { buffer->append(byte); } + SRSRAN_NODISCARD bool append(uint8_t byte) { return buffer->append(byte); } void append_zeros(size_t nof_zeros) { diff --git a/include/srsran/adt/byte_buffer_chain.h b/include/srsran/adt/byte_buffer_chain.h index 5a0fa1e848..8141141fcc 100644 --- a/include/srsran/adt/byte_buffer_chain.h +++ b/include/srsran/adt/byte_buffer_chain.h @@ -33,6 +33,8 @@ namespace srsran { /// The class iterator type automatically traverses the concatenated data_packets in a byte by byte fashion. class byte_buffer_chain { + using buffer_storage_type = std::aligned_storage_t; + template class iter_impl { @@ -145,41 +147,37 @@ class byte_buffer_chain using const_iterator = iter_impl; /// \brief Creates an empty byte_buffer_chain. - byte_buffer_chain() - { + byte_buffer_chain() : // Allocate memory block from pool for the array of slices. - mem_block.reset(detail::get_default_byte_buffer_segment_pool().allocate_node()); + mem_block(detail::get_default_byte_buffer_segment_pool().allocate_node()), + // Number of slices that fit in the memory block. + max_slices(detail::get_default_byte_buffer_segment_pool().memory_block_size() / sizeof(buffer_storage_type)) + { if (mem_block == nullptr) { srslog::fetch_basic_logger("ALL").warning("POOL: Failed to allocate memory block for byte_buffer_chain"); return; } // Initialize slice array in the allocated memory block. - size_t max_nof_slices = - detail::get_default_byte_buffer_segment_pool().memory_block_size() / sizeof(byte_buffer_slice); - byte_buffer_slice* start = static_cast(align_next(mem_block.get(), alignof(byte_buffer_slice))); - for (unsigned i = 0; i != max_nof_slices; ++i) { - new (start + i) byte_buffer_slice(); - } - slice_storage = {start, max_nof_slices}; + slices_ptr = static_cast(align_next(mem_block.get(), alignof(buffer_storage_type))); } ~byte_buffer_chain() { - // Destroy slice array. - for (auto& slice : slice_storage) { - slice.~byte_buffer_slice(); + // Destroy created slices. + for (size_t i = 0; i != slice_count; ++i) { + slices_ptr[i].~byte_buffer_slice(); } } /// Default move constructor. byte_buffer_chain(byte_buffer_chain&& other) noexcept : - byte_count(std::exchange(other.byte_count, 0)), - slice_count(std::exchange(other.slice_count, 0)), - mem_block(std::move(other.mem_block)) + byte_count(std::exchange(other.byte_count, 0U)), + mem_block(std::move(other.mem_block)), + max_slices(std::exchange(other.max_slices, 0U)), + slice_count(std::exchange(other.slice_count, 0U)), + slices_ptr(std::exchange(other.slices_ptr, nullptr)) { - slice_storage = other.slice_storage; - other.slice_storage = {}; } byte_buffer_chain(const byte_buffer_chain&) = delete; @@ -199,10 +197,16 @@ class byte_buffer_chain /// Default move assignment operator. byte_buffer_chain& operator=(byte_buffer_chain&& other) noexcept { + // Destroy created slices. + for (size_t i = 0; i != slice_count; ++i) { + slices_ptr[i].~byte_buffer_slice(); + } + // Move members from other, and set other to default values. byte_count = std::exchange(other.byte_count, 0); + mem_block = std::move(other.mem_block); + max_slices = std::exchange(other.max_slices, 0); slice_count = std::exchange(other.slice_count, 0); - std::swap(mem_block, other.mem_block); - std::swap(slice_storage, other.slice_storage); + slices_ptr = std::exchange(other.slices_ptr, nullptr); return *this; } @@ -220,13 +224,13 @@ class byte_buffer_chain } /// If the byte_buffer_chain failed to be allocated, this will return false. - bool valid() const { return not slice_storage.empty(); } + bool valid() const { return slices_ptr != nullptr; } /// Checks whether the byte_buffer_chain can append/prepend more byte_buffers. bool full() const { return nof_slices() == max_nof_slices(); } /// Checks whether the byte_buffer_chain is empty. - bool empty() const { return not valid() or nof_slices() == 0; } + bool empty() const { return nof_slices() == 0; } /// Returns the total length of the byte_buffer_chain in bytes. size_t length() const { return byte_count; } @@ -235,7 +239,7 @@ class byte_buffer_chain size_t nof_slices() const { return slice_count; } /// Returns the maximum number of byte_buffer_slices that the byte_buffer_chain can hold. - size_t max_nof_slices() const { return slice_storage.size(); } + size_t max_nof_slices() const { return max_slices; } /// Appends a byte_buffer_slice to the end of the byte_buffer_chain. /// @@ -250,7 +254,8 @@ class byte_buffer_chain return false; } byte_count += obj.length(); - slice_storage[slice_count++] = std::move(obj); + new (slices_ptr + slice_count) byte_buffer_slice(std::move(obj)); + slice_count++; return true; } @@ -260,18 +265,17 @@ class byte_buffer_chain /// Appends the contents of another byte_buffer_chain to the end of this byte_buffer_chain. /// \return true if operation was successful, false otherwise. - bool append(byte_buffer_chain&& other) + bool append(byte_buffer_chain other) { if (nof_slices() + other.nof_slices() > max_nof_slices()) { return false; } for (unsigned i = 0, end = other.nof_slices(); i != end; ++i) { - slice_storage[slice_count + i] = std::move(other.slice_storage[i]); + new (slices_ptr + slice_count) byte_buffer_slice(std::move(other.slices_ptr[i])); + slice_count++; } byte_count += other.byte_count; - slice_count += other.slice_count; - other.slice_count = 0; - other.byte_count = 0; + other.clear(); return true; } @@ -285,12 +289,19 @@ class byte_buffer_chain if (not valid() or full()) { return false; } - for (size_t i = slice_count; i > 0; --i) { - slice_storage[i] = std::move(slice_storage[i - 1]); + if (empty()) { + // In the empty case, append == prepend. + return append(std::move(slice)); + } + // Create a new slice at the right, and shift all slices to the right by 1 position. + new (slices_ptr + slice_count) byte_buffer_slice(std::move(slices_ptr[slice_count - 1])); + for (size_t i = slice_count - 1; i > 0; --i) { + slices_ptr[i] = std::move(slices_ptr[i - 1]); } byte_count += slice.length(); slice_count++; - slice_storage[0] = std::move(slice); + // Store slice in the first (now empty) position + *slices_ptr = std::move(slice); return true; } @@ -301,8 +312,9 @@ class byte_buffer_chain /// Release all the byte buffer slices held by the byte_buffer_chain. void clear() { - for (unsigned i = 0; i != nof_slices(); ++i) { - slice_storage[i] = {}; + // Note: We keep the memory block (and max_slices) to avoid new unnecessary allocations. + for (size_t i = 0; i != slice_count; ++i) { + slices_ptr[i].~byte_buffer_slice(); } slice_count = 0; byte_count = 0; @@ -320,14 +332,11 @@ class byte_buffer_chain rem_pos -= s.length(); } srsran_assertion_failure("Out-of-bounds access ({} >= {})", idx, length()); - return slice_storage[0][0]; + return slices_ptr[0][0]; } /// Returns the slices of the byte_buffer_chain. - span slices() const - { - return span{slice_storage.begin(), slice_storage.begin() + slice_count}; - } + span slices() const { return span{slices_ptr, slice_count}; } template bool operator==(const Range& other) const @@ -344,25 +353,18 @@ class byte_buffer_chain /// Returns an iterator to the begin of the byte_buffer_chain. iterator begin() { - return iterator{*this, - slice_storage.begin(), - empty() ? byte_buffer_view::iterator{nullptr, 0} : slice_storage.begin()->begin()}; + return iterator{*this, slices_ptr, empty() ? byte_buffer_view::iterator{nullptr, 0} : slices_ptr->begin()}; } const_iterator begin() const { - return const_iterator{*this, - slice_storage.begin(), - empty() ? byte_buffer_view::iterator{nullptr, 0} : slice_storage.begin()->begin()}; + return const_iterator{*this, slices_ptr, empty() ? byte_buffer_view::iterator{nullptr, 0} : slices_ptr->begin()}; } /// Returns an iterator to the end of the byte_buffer_chain. - iterator end() - { - return iterator{*this, slice_storage.begin() + slice_count, byte_buffer_view::iterator{nullptr, 0}}; - } + iterator end() { return iterator{*this, slices_ptr + slice_count, byte_buffer_view::iterator{nullptr, 0}}; } const_iterator end() const { - return const_iterator{*this, slice_storage.begin() + slice_count, byte_buffer_view::iterator{nullptr, 0}}; + return const_iterator{*this, slices_ptr + slice_count, byte_buffer_view::iterator{nullptr, 0}}; } private: @@ -377,12 +379,14 @@ class byte_buffer_chain // Total number of bytes stored in this container. size_t byte_count = 0; - // Total number of byte_buffer_slices stored in this container. - size_t slice_count = 0; // Memory block managed by a memory pool, where the slices are stored. std::unique_ptr mem_block; + // Maximum number of byte_buffer_slices that this container can hold. + size_t max_slices = 0; + // Total number of byte_buffer_slices stored in this container. + size_t slice_count = 0; // Array where byte_buffer_slices are stored. This array is a view to the \c mem_block. - span slice_storage; + byte_buffer_slice* slices_ptr = nullptr; }; } // namespace srsran diff --git a/include/srsran/adt/concurrent_queue.h b/include/srsran/adt/concurrent_queue.h index f3f730af81..0f59d84431 100644 --- a/include/srsran/adt/concurrent_queue.h +++ b/include/srsran/adt/concurrent_queue.h @@ -22,113 +22,248 @@ #pragma once +#include "rigtorp/MPMCQueue.h" #include "rigtorp/SPSCQueue.h" #include "srsran/adt/blocking_queue.h" +#include "srsran/adt/detail/tuple_utils.h" #include "srsran/adt/optional.h" #include "srsran/support/compiler.h" namespace srsran { /// \brief Types of concurrent queues. They differ in type of synchronization mechanism and number of -/// producers/consumers. Supported types are: -/// - lockfree_spsc: lockfree single producer single consumer queue. -/// - locking_mpmc: multiple producer multiple consumer queue that uses a mutex for synchronization. It is the most -/// generic type of queue, but it is also the slowest. It relies on a condition variable to wake up producers and +/// producers/consumers supported. Supported types are: +/// - lockfree_spsc: lockfree single producer single consumer queue (SPSC). +/// - lockfree_mpmc: lockfree multiple producer multiple consumer queue (MPMC). +/// - locking_mpmc: multiple producer multiple consumer (MPMC) queue that uses a mutex for synchronization. It is the +/// most generic type of queue, but it is also the slowest. It relies on a condition variable to wake up producers and /// consumers. /// - locking_mpsc: similar to the locking_mpmc, but it leverages batch popping on the consumer side, to reduce /// mutex contention. -enum class concurrent_queue_policy { lockfree_spsc, locking_mpmc, locking_mpsc }; +enum class concurrent_queue_policy { lockfree_spsc, lockfree_mpmc, locking_mpmc, locking_mpsc }; -/// Types of barriers used for blocking pushes/pops of elements. -enum class concurrent_queue_wait_policy { condition_variable, sleep }; +/// \brief Types of barriers used for blocking pushes/pops of elements. Three types: +/// - condition_variable: uses a condition variable to wake up producers and consumers. +/// - sleep: spins on a sleep if the queue is full, in case of blocking push, and if the queue is empty in case of +/// blocking pop. +/// - non_blocking: no blocking mechanism is exposed. +enum class concurrent_queue_wait_policy { condition_variable, sleep, non_blocking }; namespace detail { template class queue_impl; -// Specialization for lockfree SPSC using a sleep as blocking mechanism. +// Specialization for lockfree SPSC without blocking mechanism. +template +class queue_impl +{ +public: + template + explicit queue_impl(size_t qsize) : queue(qsize) + { + } + + template + bool try_push(U&& elem) + { + return queue.try_push(std::forward(elem)); + } + + bool try_pop(T& elem) + { + T* front = queue.front(); + if (front != nullptr) { + elem = std::move(*front); + queue.pop(); + return true; + } + return false; + } + + template + bool try_call_on_pop(PoppingFunc&& func) + { + T* front = queue.front(); + if (front != nullptr) { + func(*front); + queue.pop(); + return true; + } + return false; + } + + size_t size() const { return queue.size(); } + + bool empty() const { return queue.empty(); } + + size_t capacity() const { return queue.capacity(); } + +protected: + rigtorp::SPSCQueue queue; +}; + +// Specialization for lockfree SPSC using a spin sleep loop as blocking mechanism. template class queue_impl + : public queue_impl { + using base_type = queue_impl; + public: template - explicit queue_impl(size_t qsize, std::chrono::microseconds sleep_time_ = std::chrono::microseconds{0}) : - queue(qsize), sleep_time(sleep_time_) + explicit queue_impl(size_t qsize, std::chrono::microseconds sleep_time_) : + queue_impl(qsize), + sleep_time(sleep_time_) { } void request_stop() { running = false; } - template - bool push(U&& elem) + template + bool push_blocking(U&& elem) { while (running.load(std::memory_order_relaxed)) { - if (queue.try_push(std::forward(elem))) { + if (this->try_push(std::forward(elem))) { return true; } - if (BlockOnFull) { - std::this_thread::sleep_for(sleep_time); - } else { - break; - } + std::this_thread::sleep_for(sleep_time); } return false; } - template - optional pop() + bool pop_blocking(T& elem) noexcept { - optional ret; - T* f = front(); + T* f = front_blocking(); if (f != nullptr) { - ret = std::move(*f); - queue.pop(); - return ret; + elem = std::move(*f); + this->queue.pop(); + return true; } - return nullopt; + return false; } - template - bool call_and_pop(const PoppingFunc& func) + template + bool call_on_pop_blocking(PoppingFunc&& func) { - T* ret = front(); - if (ret != nullptr) { - func(*ret); - queue.pop(); + T* f = front_blocking(); + if (f != nullptr) { + func(*f); + this->queue.pop(); return true; } return false; } - size_t size() const { return queue.size(); } +private: + T* front_blocking() + { + while (running.load(std::memory_order_relaxed)) { + T* front = this->queue.front(); + if (front != nullptr) { + return front; + } + std::this_thread::sleep_for(sleep_time); + } + return nullptr; + } + + std::chrono::microseconds sleep_time; + std::atomic running{true}; +}; + +// Specialization for lockfree MPMC using no blocking mechanism. +template +class queue_impl +{ +public: + template + explicit queue_impl(size_t qsize) : queue(qsize) + { + } + + bool try_push(const T& elem) { return queue.try_push(elem); } + bool try_push(T&& elem) { return queue.try_push(std::move(elem)); } + + bool try_pop(T& elem) { return queue.try_pop(elem); } + + template + bool try_call_on_pop(PoppingFunc&& func) + { + T t; + if (queue.try_pop(t)) { + func(t); + return true; + } + return false; + } + + size_t size() const + { + // Note: MPMCqueue size can be negative. + ptrdiff_t ret = queue.size(); + return static_cast(std::max(ret, static_cast(0))); + } bool empty() const { return queue.empty(); } size_t capacity() const { return queue.capacity(); } - void clear() +protected: + rigtorp::MPMCQueue queue; +}; + +// Specialization for lockfree MPMC using a sleep as blocking mechanism. +template +class queue_impl + : public queue_impl +{ + using base_type = queue_impl; + +public: + template + explicit queue_impl(size_t qsize, std::chrono::microseconds sleep_time_ = std::chrono::microseconds{0}) : + base_type(qsize), sleep_time(sleep_time_) { - while (queue.front()) { - queue.pop(); + } + + void request_stop() { running = false; } + + template + bool push_blocking(U&& elem) + { + while (running.load(std::memory_order_relaxed)) { + if (this->try_push(std::forward(elem))) { + return true; + } + std::this_thread::sleep_for(sleep_time); } + return false; } -private: - template - T* front() + bool pop_blocking(T& elem) { while (running.load(std::memory_order_relaxed)) { - T* front = queue.front(); - if (not BlockOnEmpty or front != nullptr) { - return front; + if (this->try_pop(elem)) { + return true; } std::this_thread::sleep_for(sleep_time); } - return nullptr; + return false; + } + + template + bool call_on_pop_blocking(PoppingFunc&& func) + { + T elem; + if (pop_blocking(elem)) { + func(elem); + return true; + } + return false; } - rigtorp::SPSCQueue queue; +private: std::chrono::microseconds sleep_time; std::atomic running{true}; }; @@ -142,51 +277,48 @@ class queue_impl - bool push(const T& elem) noexcept - { - if (BlockOnFull) { - return queue.push_blocking(elem); - } - return queue.try_push(elem); - } + bool try_push(const T& elem) { return queue.try_push(elem); } + bool try_push(T&& elem) { return not queue.try_push(std::move(elem)).is_error(); } + bool push_blocking(const T& elem) { return queue.push_blocking(elem); } + bool push_blocking(T&& elem) { return not queue.push_blocking(std::move(elem)).is_error(); } - template - bool push(T&& elem) noexcept + optional try_pop() { - if (BlockOnFull) { - return queue.push_blocking(std::move(elem)).has_value(); + optional t; + t.emplace(); + if (not queue.try_pop(t.value())) { + t.reset(); } - return queue.try_push(std::move(elem)).has_value(); + return t; } - template - optional pop() + bool try_pop(T& elem) { return queue.try_pop(elem); } + + template + bool try_call_on_pop(PoppingFunc&& func) { T t; - if (BlockOnEmpty) { - bool success = false; - t = queue.pop_blocking(&success); - return success ? optional{std::move(t)} : optional{}; - } if (queue.try_pop(t)) { - return optional{std::move(t)}; + func(t); + return true; } - return nullopt; + return false; } - template - bool call_and_pop(const PoppingFunc& func) + bool pop_blocking(T& elem) { bool success = false; - T t; - if (BlockOnFull) { - t = queue.pop_blocking(&success); - } else { - success = queue.try_pop(t); - } + elem = queue.pop_blocking(&success); + return success; + } + + template + bool call_on_pop_blocking(PoppingFunc&& func) + { + bool success = false; + T elem = queue.pop_blocking(&success); if (success) { - func(t); + func(elem); return true; } return false; @@ -292,67 +424,95 @@ class queue_impl public: template - explicit queue_impl(size_t qsize, Args&&... args) : cap(qsize), barrier(std::forward(args)...) + explicit queue_impl(size_t qsize, Args&&... args) : + cap(qsize), + queues({ring_buffer{(unsigned)qsize}, ring_buffer{(unsigned)qsize}}), + barrier(std::forward(args)...) { - queue.reserve(qsize); - popped_items.reserve(qsize); } void request_stop() { std::lock_guard lock(mutex); - queue.clear(); + pushing_queue().clear(); barrier.request_stop(); } - template - bool push(U&& elem) noexcept + template + bool try_push(U&& elem) { std::unique_lock lock(mutex); - if (BlockOnFull) { - barrier.wait_push(lock, [this]() { return queue.size() < cap; }); + if (barrier.is_running() and not pushing_queue().full()) { + pushing_queue().push(std::forward(elem)); + barrier.notify_push(); + return true; } - if (barrier.is_running() and queue.size() < cap) { - queue.push_back(std::forward(elem)); + return false; + } + + template + bool push_blocking(U&& elem) + { + std::unique_lock lock(mutex); + barrier.wait_push(lock, [this]() { return not pushing_queue().full(); }); + if (barrier.is_running()) { + pushing_queue().push(std::forward(elem)); barrier.notify_push(); return true; } return false; } - template - optional pop() + bool try_pop(T& elem) { - T* item = pop_(); - return item != nullptr ? optional{std::move(*item)} : optional{}; + T* f = front(); + if (f != nullptr) { + elem = std::move(*f); + pop(); + return true; + } + return false; } - template - bool call_and_pop(const PoppingFunc& func) + bool pop_blocking(T& elem) noexcept { - T* item = pop_(); - if (item != nullptr) { - func(*item); + T* f = front(); + if (f != nullptr) { + elem = std::move(*f); + pop(); return true; } return false; } - void clear() + template + bool try_call_on_pop(PoppingFunc&& func) { - { - std::lock_guard lock(mutex); - queue.clear(); + T* f = front(); + if (f != nullptr) { + func(*f); + pop(); + return true; } - count_local_objs.store(0, std::memory_order_relaxed); - popped_items.clear(); - barrier.notify_pop(); + return false; + } + + template + bool call_on_pop_blocking(PoppingFunc&& func) + { + T* f = front(); + if (f != nullptr) { + func(*f); + pop(); + return true; + } + return false; } size_t size() const { std::lock_guard lock(mutex); - return queue.size() + count_local_objs.load(std::memory_order_relaxed); + return pushing_queue().size() + count_local_objs.load(std::memory_order_relaxed); } bool empty() const @@ -361,42 +521,56 @@ class queue_impl return false; } std::lock_guard lock(mutex); - return queue.empty(); + return pushing_queue().empty(); } size_t capacity() const { return cap; } private: template - T* pop_() + T* front() { - unsigned count = count_local_objs.load(std::memory_order_relaxed); - if (barrier.is_running() and count > 0) { - count_local_objs.fetch_sub(1, std::memory_order_relaxed); - T* t = &popped_items[popped_items.size() - count]; - return t; + if (not barrier.is_running()) { + return nullptr; + } + if (not popping_queue().empty()) { + // Successful pop. + return &popping_queue().top(); } - popped_items.clear(); { std::unique_lock lock(mutex); if (Blocking) { - barrier.wait_pop(lock, [this]() { return !queue.empty(); }); - } - if (not barrier.is_running() or queue.empty()) { + barrier.wait_pop(lock, [this]() { return !pushing_queue().empty(); }); + if (not barrier.is_running()) { + return nullptr; + } + } else if (pushing_queue().empty()) { return nullptr; } - popped_items.swap(queue); + // Swap queues. + index_queue_for_pop = 1 - index_queue_for_pop; } barrier.notify_pop(); - count_local_objs.store(popped_items.size() - 1, std::memory_order_relaxed); - return &popped_items[0]; + count_local_objs.store(popping_queue().size(), std::memory_order_relaxed); + return &popping_queue().top(); } - const size_t cap; - std::atomic count_local_objs{0}; - std::vector queue, popped_items; - mutable std::mutex mutex; - queue_barrier barrier; + void pop() + { + popping_queue().pop(); + count_local_objs.fetch_sub(1, std::memory_order_relaxed); + } + + ring_buffer& popping_queue() { return queues[index_queue_for_pop]; } + ring_buffer& pushing_queue() { return queues[1 - index_queue_for_pop]; } + const ring_buffer& pushing_queue() const { return queues[1 - index_queue_for_pop]; } + + const size_t cap; + std::atomic count_local_objs{0}; + std::array, 2> queues; + unsigned index_queue_for_pop = 0; + mutable std::mutex mutex; + queue_barrier barrier; }; } // namespace detail @@ -425,49 +599,227 @@ class concurrent_queue /// Pushes a new element into the queue in a non-blocking fashion. If the queue is full, the element is not pushed. /// \return true if the element was pushed, false otherwise. - bool try_push(T&& elem) noexcept { return queue.template push(std::move(elem)); } - bool try_push(const T& elem) noexcept { return queue.template push(elem); } + template + bool try_push(U&& elem) noexcept + { + return queue.try_push(std::forward(elem)); + } /// Pushes a new element into the queue. If the queue is full, the call blocks, waiting for a new slot to become /// emptied. /// \return true if the element was pushed, false if the queue was closed. - bool push_blocking(T&& elem) noexcept { return queue.template push(std::move(elem)); } - bool push_blocking(const T& elem) noexcept { return queue.template push(elem); } + template = 0> + bool push_blocking(U&& elem) noexcept + { + return queue.push_blocking(std::forward(elem)); + } - /// Pops an element from the queue in a non-blocking fashion. If the queue is empty, the call returns an empty - /// optional. - optional try_pop() { return queue.template pop(); } + /// \brief Pops an element from the queue in a non-blocking fashion. + /// + /// If the queue is empty, the call returns false. + bool try_pop(T& elem) { return queue.try_pop(elem); } - /// Pops an element from the queue. If the queue is empty, the call blocks, waiting for a new element to be pushed. - optional pop_blocking() { return queue.template pop(); } + /// \brief Pops an element from the queue in a non-blocking fashion. + /// + /// If the queue is empty, the call returns an empty optional. + optional try_pop() + { + optional ret; + queue.try_call_on_pop([&ret](T& t) mutable { ret = std::move(t); }); + return ret; + } - /// \brief Pops an element from the queue and calls the provided function with the popped element. If the queue is - /// empty, the call returns false. Otherwise, it returns true. + /// \brief Pops an element from the queue and calls the provided function with the popped element. + /// + /// If the queue is empty, the call returns false, and the CallOnPop function is not called. Otherwise, it returns + /// true. template - bool try_pop(const CallOnPop& func) + bool try_call_on_pop(const CallOnPop& func) + { + return queue.try_call_on_pop(func); + } + + /// Pops an element from the queue. If the queue is empty, the call blocks, waiting for a new element to be pushed. + bool pop_blocking(T& elem) { return queue.pop_blocking(elem); } + + /// Pops an element from the queue. If the queue is empty, the call blocks, waiting for a new element to be pushed. + optional pop_blocking() noexcept { - return queue.template call_and_pop(func); + optional ret; + queue.call_on_pop_blocking([&ret](T& t) mutable { ret = std::move(t); }); + return ret; } /// \brief Pops an element from the queue and calls the provided function with the popped element. If the queue is /// empty, the function blocks, waiting for a new element to be pushed. It returns false if the queue is closed. template - bool pop_blocking(const CallOnPop& func) + bool call_on_pop_blocking(const CallOnPop& func) { - return queue.template call_and_pop(func); + return queue.call_on_pop_blocking(func); } /// \brief Maximum capacity of the queue. size_t capacity() const { return queue.capacity(); } + /// \brief Current size of the queue. size_t size() const { return queue.size(); } + /// \brief Determines whether the queue is empty. bool empty() const { return queue.empty(); } + /// \brief Request any blocking function to be interrupted. void request_stop() { queue.request_stop(); } private: queue_type queue; }; +/// \brief Queue priority used to map to specific queue of the \c priority_multiqueue_task_worker. The higher the +/// priority, the lower its integer value representation. +enum class enqueue_priority : size_t { min = 0, max = std::numeric_limits::max() }; + +/// Reduce priority by \c dec amount. +constexpr enqueue_priority operator-(enqueue_priority lhs, size_t dec) +{ + return static_cast(static_cast(lhs) - dec); +} + +namespace detail { + +static constexpr size_t enqueue_priority_to_queue_index(enqueue_priority prio, size_t nof_priority_levels) +{ + size_t queue_idx = std::numeric_limits::max() - static_cast(prio); + return queue_idx < nof_priority_levels ? queue_idx : nof_priority_levels - 1; +} + +static constexpr enqueue_priority queue_index_to_enqueue_priority(size_t queue_idx, size_t nof_priority_levels) +{ + return static_cast(std::numeric_limits::max() - queue_idx); +} + +} // namespace detail + +/// \brief Concurrent priority queue, where the caller specifies the element priority statically while pushing it to +/// the queue. +/// +/// The prioritization is achieved via multiple queues. The pop functions will always start with the highest priority +/// queue until it is depleted, and then move to the second highest priority queue, and so on. +/// \tparam T The type of the pushed/popped object. +/// \tparam QueuePolicies The queueing policies of each of the queues of different priorities. +template +class concurrent_priority_queue +{ +public: + /// \brief Construct a new prioritized queue. + /// \param task_queue_sizes The sizes of the task queues for each priority level. + /// \param wait_interval The amount of time to suspend the thread, when no tasks are pending. + concurrent_priority_queue(const std::array& priority_queue_sizes, + std::chrono::microseconds wait_interval_) : + queues(detail::as_tuple(priority_queue_sizes)), wait_interval(wait_interval_) + { + } + + /// \brief Push new object with priority level \c Priority. + template + SRSRAN_NODISCARD bool try_push(U&& u) + { + return std::get(queues).try_push( + std::forward(u)); + } + + /// \brief Push new object with priority level \c Priority. + template + SRSRAN_NODISCARD bool push_blocking(U&& u) + { + while (running.load(std::memory_order_relaxed)) { + if (try_push(std::forward(u))) { + return true; + } + std::this_thread::sleep_for(wait_interval); + } + return false; + } + + /// \brief Pop object from queue, by starting first with the objects with highest priority. + bool try_pop(T& elem) + { + return detail::any_of(queues, [&elem](auto& queue) mutable { return queue.try_pop(elem); }); + } + + optional try_pop() + { + optional elem; + detail::any_of(queues, [&elem](auto& queue) mutable { + elem = queue.try_pop(); + return elem.has_value(); + }); + return elem; + } + + /// \brief Call function on object popped from the queue. + template + bool try_call_on_pop(const CallOnPop& func) + { + return detail::any_of(queues, [&func](auto& queue) mutable { return queue.try_call_on_pop(func); }); + } + + bool pop_blocking(T& elem) + { + while (running.load(std::memory_order_relaxed)) { + if (try_pop(elem)) { + return true; + } + std::this_thread::sleep_for(wait_interval); + } + return false; + } + + optional pop_blocking() + { + optional t; + while (running.load(std::memory_order_relaxed)) { + t = try_pop(); + if (t.has_value()) { + break; + } + std::this_thread::sleep_for(wait_interval); + } + return t; + } + + template + bool call_on_pop_blocking(const CallOnPop& func) + { + while (running.load(std::memory_order_relaxed)) { + if (try_call_on_pop(func)) { + return true; + } + std::this_thread::sleep_for(wait_interval); + } + return false; + } + + void request_stop() { running = false; } + + /// \brief Get specified priority queue capacity. + template + SRSRAN_NODISCARD size_t capacity() const + { + return std::get(queues).capacity(); + } + + /// Number of priority levels supported by this queue. + static constexpr size_t nof_priority_levels() { return sizeof...(QueuePolicies); } + +private: + std::tuple...> queues; + + // Time that thread spends sleeping when there are no pending tasks. + const std::chrono::microseconds wait_interval; + + std::atomic running{true}; +}; + } // namespace srsran \ No newline at end of file diff --git a/include/srsran/adt/detail/tuple_utils.h b/include/srsran/adt/detail/tuple_utils.h new file mode 100644 index 0000000000..3629ba7a95 --- /dev/null +++ b/include/srsran/adt/detail/tuple_utils.h @@ -0,0 +1,83 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include + +namespace srsran { + +namespace detail { + +template +constexpr auto as_tuple(const std::array& arr, std::index_sequence /*unused*/) +{ + return std::make_tuple(arr[Is]...); +} + +/// Convert an std::array to a tuple. +template +constexpr auto as_tuple(const std::array& arr) +{ + return as_tuple(arr, std::make_index_sequence{}); +} + +template +constexpr void for_each_impl(T&& t, const F& f, std::index_sequence) +{ + (void)std::initializer_list{(f(std::get(t)), 0)...}; +} + +template +constexpr void for_each(T&& t, const F& f) +{ + for_each_impl(std::forward(t), f, std::make_index_sequence>::value>{}); +} + +template +constexpr bool any_of_impl(Tuple&& /*unused*/, Pred&& /*unused*/, std::index_sequence<>) +{ + return false; +} + +template +constexpr bool any_of_impl(Tuple&& t, Pred&& pred, std::index_sequence) +{ + return pred(std::get(t)) || + any_of_impl(std::forward(t), std::forward(pred), std::index_sequence{}); +} + +template +constexpr bool any_of(std::tuple& t, Pred&& pred) +{ + return any_of_impl(t, std::forward(pred), std::index_sequence_for{}); +} + +template +constexpr bool any_of(const std::tuple& t, Pred&& pred) +{ + return any_of_impl(t, std::forward(pred), std::index_sequence_for{}); +} + +} // namespace detail + +} // namespace srsran \ No newline at end of file diff --git a/include/srsran/adt/span.h b/include/srsran/adt/span.h index 9d9c1c7ada..a368f7298d 100644 --- a/include/srsran/adt/span.h +++ b/include/srsran/adt/span.h @@ -22,6 +22,7 @@ #pragma once +#include "srsran/adt/static_vector.h" #include "fmt/format.h" #include #include @@ -322,11 +323,63 @@ struct formatter> { } template - auto format(const srsran::span& buf, FormatContext& ctx) + auto format(srsran::span buf, FormatContext& ctx) { string_view format_str = string_view(format_buffer.data(), format_buffer.size()); string_view delimiter_str = string_view(delimiter_buffer.data(), delimiter_buffer.size()); return format_to(ctx.out(), format_str, fmt::join(buf.begin(), buf.end(), delimiter_str)); } }; + +/// Custom formatter used by the \c copy_loggable_type defined below. +template +struct formatter> : public formatter> { + using formatter>::delimiter_buffer; + using formatter>::format_buffer; + + template + auto format(const std::vector& buf, FormatContext& ctx) + { + string_view format_str = string_view(format_buffer.data(), format_buffer.size()); + string_view delimiter_str = string_view(delimiter_buffer.data(), delimiter_buffer.size()); + return format_to(ctx.out(), format_str, fmt::join(buf.begin(), buf.end(), delimiter_str)); + } +}; + +/// Custom formatter used by the \c copy_loggable_type defined below. +template +struct formatter> : public formatter> { + using formatter>::delimiter_buffer; + using formatter>::format_buffer; + + template + auto format(const srsran::static_vector& buf, FormatContext& ctx) + { + string_view format_str = string_view(format_buffer.data(), format_buffer.size()); + string_view delimiter_str = string_view(delimiter_buffer.data(), delimiter_buffer.size()); + return format_to(ctx.out(), format_str, fmt::join(buf.begin(), buf.end(), delimiter_str)); + } +}; + } // namespace fmt + +namespace srslog { + +/// Type trait specialization to instruct the logger to use a user defined copy implementation as it is unsafe to +/// directly copy the contents of a span. +template +struct copy_loggable_type> { + static constexpr bool is_copyable = false; + + static void copy(fmt::dynamic_format_arg_store* store, srsran::span s) + { + static constexpr unsigned MAX_NOF_ELEMENTS = 128; + if (s.size() < MAX_NOF_ELEMENTS) { + store->push_back(srsran::static_vector, MAX_NOF_ELEMENTS>(s.begin(), s.end())); + } else { + store->push_back(std::vector>(s.begin(), s.end())); + } + } +}; + +} // namespace srslog diff --git a/include/srsran/asn1/asn1_utils.h b/include/srsran/asn1/asn1_utils.h index aa9c77097b..2eda2e0d57 100644 --- a/include/srsran/asn1/asn1_utils.h +++ b/include/srsran/asn1/asn1_utils.h @@ -2063,7 +2063,7 @@ class elementary_procedure_option ProtocolIEs protocol_ies; public: - bool ext; + bool ext = false; // ... ProtocolIEs* operator->() { return &protocol_ies; } diff --git a/include/srsran/cu_cp/cu_cp_configuration.h b/include/srsran/cu_cp/cu_cp_configuration.h index 1583834063..b5e4232eec 100644 --- a/include/srsran/cu_cp/cu_cp_configuration.h +++ b/include/srsran/cu_cp/cu_cp_configuration.h @@ -51,6 +51,7 @@ struct cu_cp_configuration { ue_configuration ue_config; mobility_configuration mobility_config; security_indication_t default_security_indication; // default if not signaled via NGAP + std::chrono::seconds statistics_report_period; // CU-CP statistics report period in seconds }; } // namespace srs_cu_cp diff --git a/include/srsran/cu_cp/cu_cp_types.h b/include/srsran/cu_cp/cu_cp_types.h index 13b7e518ed..7707b69505 100644 --- a/include/srsran/cu_cp/cu_cp_types.h +++ b/include/srsran/cu_cp/cu_cp_types.h @@ -144,14 +144,6 @@ struct cu_cp_ue_creation_message { bool is_inter_cu_handover = false; }; -// Globally unique AMF identifier. -struct guami_t { - optional plmn; - uint16_t amf_set_id; - uint8_t amf_pointer; - uint8_t amf_region_id; -}; - /// QoS Configuration, i.e. 5QI and the associated PDCP /// and SDAP configuration for DRBs struct cu_cp_qos_config { @@ -165,6 +157,32 @@ struct cu_cp_tai { uint32_t tac; }; +struct cu_cp_user_location_info_nr { + nr_cell_global_id_t nr_cgi; + cu_cp_tai tai; + optional time_stamp; +}; + +struct cu_cp_five_g_s_tmsi { + uint16_t amf_set_id; + uint8_t amf_pointer; + uint32_t five_g_tmsi; +}; + +struct cu_cp_initial_ue_message { + ue_index_t ue_index = ue_index_t::invalid; + byte_buffer nas_pdu; + establishment_cause_t establishment_cause; + cu_cp_user_location_info_nr user_location_info; + optional five_g_s_tmsi; +}; + +struct cu_cp_ul_nas_transport { + ue_index_t ue_index = ue_index_t::invalid; + byte_buffer nas_pdu; + cu_cp_user_location_info_nr user_location_info; +}; + struct cu_cp_tx_bw { subcarrier_spacing nr_scs; uint16_t nr_nrb; @@ -228,12 +246,6 @@ struct cu_cp_du_served_cells_item { optional gnb_du_sys_info; // not optional for NG-RAN }; -struct cu_cp_user_location_info_nr { - nr_cell_global_id_t nr_cgi; - cu_cp_tai tai; - optional time_stamp; -}; - struct cu_cp_alloc_and_retention_prio { uint8_t prio_level_arp; std::string pre_emption_cap; @@ -461,6 +473,13 @@ struct cu_cp_recommended_cells_for_paging { std::vector recommended_cell_list; }; +struct cu_cp_ue_context_release_command { + ue_index_t ue_index = ue_index_t::invalid; + cause_t cause; + byte_buffer rrc_release_pdu; + optional srb_id; +}; + struct cu_cp_global_gnb_id { std::string plmn_id; std::string gnb_id; @@ -494,12 +513,6 @@ struct cu_cp_ue_context_release_complete { optional crit_diagnostics; }; -struct cu_cp_five_g_s_tmsi { - uint16_t amf_set_id; - uint8_t amf_pointer; - uint32_t five_g_tmsi; -}; - struct cu_cp_tai_list_for_paging_item { cu_cp_tai tai; }; diff --git a/include/srsran/cu_cp/cu_up_processor.h b/include/srsran/cu_cp/cu_up_processor.h index c7fc29f7cc..8b803ce9d3 100644 --- a/include/srsran/cu_cp/cu_up_processor.h +++ b/include/srsran/cu_cp/cu_up_processor.h @@ -56,8 +56,16 @@ class cu_up_processor_e1ap_interface virtual e1ap_message_handler& get_e1ap_message_handler() = 0; /// \brief Get the E1AP bearer context manager interface. - /// \return The E1AP bearer context manager interface of the CU-UP processor object. + /// \return The E1AP bearer context manager interface of the CU-UP processor object. virtual e1ap_bearer_context_manager& get_e1ap_bearer_context_manager() = 0; + + /// \brief Get the E1AP bearer context removal interface. + /// \return The E1AP bearer context removal interface of the CU-UP processor object. + virtual e1ap_bearer_context_removal_handler& get_e1ap_bearer_context_removal_handler() = 0; + + /// \brief Get the E1AP statistic interface. + /// \return The E1AP statistic interface of the CU-UP processor object. + virtual e1ap_statistics_handler& get_e1ap_statistics_handler() = 0; }; /// Methods used by CU-UP processor to notify about CU-UP specific events. diff --git a/include/srsran/cu_cp/cu_up_repository.h b/include/srsran/cu_cp/cu_up_repository.h index 952d54e253..c17849763e 100644 --- a/include/srsran/cu_cp/cu_up_repository.h +++ b/include/srsran/cu_cp/cu_up_repository.h @@ -42,6 +42,10 @@ class cu_up_handler /// \return The E1AP bearer context manager interface of the CU-UP processor object. virtual e1ap_bearer_context_manager& get_e1ap_bearer_context_manager() = 0; + /// \brief Get the E1AP bearer context removal interface. + /// \return The E1AP bearer context removal interface of the CU-UP processor object. + virtual e1ap_bearer_context_removal_handler& get_e1ap_bearer_context_removal_handler() = 0; + /// \brief Update the index of an UE. virtual void update_ue_index(ue_index_t ue_index, ue_index_t old_ue_index) = 0; }; diff --git a/include/srsran/cu_cp/du_processor.h b/include/srsran/cu_cp/du_processor.h index 4f275dd628..dcc1f50174 100644 --- a/include/srsran/cu_cp/du_processor.h +++ b/include/srsran/cu_cp/du_processor.h @@ -84,7 +84,7 @@ class du_processor_f1ap_interface virtual f1ap_statistics_handler& get_f1ap_statistics_handler() = 0; }; -/// Interface to notifiy UE context management procedures. +/// Interface to notify UE context management procedures. class du_processor_f1ap_ue_context_notifier { public: @@ -105,9 +105,14 @@ class du_processor_f1ap_ue_context_notifier /// 'true' in case of a successful outcome, 'false' otherwise. virtual async_task on_ue_context_modification_request(const f1ap_ue_context_modification_request& request) = 0; + + /// \brief Notify the F1AP that the given UE corresponds to a reestablishment session of the old UE. + /// \param[in] ue_index The index of the UE that is performing a reestablishment. + /// \param[in] old_ue_index The index of the UE that + virtual bool on_intra_du_reestablishment(ue_index_t ue_index, ue_index_t old_ue_index) = 0; }; -/// Interface to notifiy Paging procedures. +/// Interface to notify Paging procedures. class du_processor_f1ap_paging_notifier { public: @@ -141,7 +146,7 @@ class du_processor_rrc_interface virtual rrc_amf_connection_handler& get_rrc_amf_connection_handler() = 0; }; -/// Interface to notifiy RRC DU about UE management procedures. +/// Interface to notify RRC DU about UE management procedures. class du_processor_rrc_du_ue_notifier { public: @@ -159,10 +164,6 @@ class du_processor_rrc_du_ue_notifier virtual rrc_ue_interface* on_ue_creation_request(up_resource_manager& resource_mng, const rrc_ue_creation_message& msg) = 0; - /// \brief Notify the RRC DU to release a UE. - /// \param[in] ue_index The index of the UE object to remove. - virtual void on_ue_context_release_command(ue_index_t ue_index) = 0; - /// Send RRC Release to all UEs connected to this DU. virtual void on_release_ues() = 0; }; @@ -176,7 +177,7 @@ class du_processor_rrc_ue_interface /// \brief Handle a UE Context Release Command /// \param[in] cmd The UE Context Release Command. virtual async_task - handle_ue_context_release_command(const rrc_ue_context_release_command& cmd) = 0; + handle_ue_context_release_command(const cu_cp_ue_context_release_command& cmd) = 0; /// \brief Handle a required reestablishment context modification. /// \param[in] ue_index The index of the UE that needs the context modification. @@ -337,14 +338,13 @@ class du_processor_f1ap_control_notifier }; /// \brief Schedules asynchronous tasks associated with an UE. -class du_processor_ue_task_scheduler +class du_processor_ue_task_scheduler : public f1ap_task_scheduler { public: - virtual ~du_processor_ue_task_scheduler() = default; - virtual void schedule_async_task(ue_index_t ue_index, async_task&& task) = 0; - virtual void clear_pending_tasks(ue_index_t ue_index) = 0; - virtual unique_timer make_unique_timer() = 0; - virtual timer_manager& get_timer_manager() = 0; + virtual ~du_processor_ue_task_scheduler() = default; + virtual void clear_pending_tasks(ue_index_t ue_index) = 0; + virtual unique_timer make_unique_timer() = 0; + virtual timer_manager& get_timer_manager() = 0; }; /// \brief Handles incoming task scheduling requests associated with an UE. @@ -363,11 +363,26 @@ class du_processor_cu_cp_notifier public: virtual ~du_processor_cu_cp_notifier() = default; - /// \brief Notifies about a successful RRC UE creation. + /// \brief Notifies about a successful F1AP and RRC creation. /// \param[in] du_index The index of the DU the UE is connected to. + /// \param[in] f1ap_handler Handler to the F1AP to initiate the UE context removal. + /// \param[in] f1ap_statistic_handler Handler to the F1AP statistic interface. + /// \param[in] rrc_handler Handler to the RRC DU to initiate the RRC UE removal. + /// \param[in] rrc_statistic_handler Handler to the RRC DU statistic interface. + virtual void on_du_processor_created(du_index_t du_index, + f1ap_ue_context_removal_handler& f1ap_handler, + f1ap_statistics_handler& f1ap_statistic_handler, + rrc_ue_removal_handler& rrc_handler, + rrc_du_statistics_handler& rrc_statistic_handler) = 0; + + /// \brief Notifies about a successful RRC UE creation. /// \param[in] ue_index The index of the UE. /// \param[in] rrc_ue_msg_handler The created RRC UE. - virtual void on_rrc_ue_created(du_index_t du_index, ue_index_t ue_index, rrc_ue_interface& rrc_ue) = 0; + virtual void on_rrc_ue_created(ue_index_t ue_index, rrc_ue_interface& rrc_ue) = 0; + + /// \brief Notify the CU-CP to completly remove a UE from the CU-CP. + /// \param[in] ue_index The index of the UE to remove. + virtual void on_ue_removal_required(ue_index_t ue_index) = 0; }; /// DU processor Paging handler. @@ -390,15 +405,6 @@ class du_processor_inactivity_handler virtual void handle_inactivity_notification(const cu_cp_inactivity_notification& msg) = 0; }; -class du_processor_ue_handler -{ -public: - virtual ~du_processor_ue_handler() = default; - - /// \brief Removes a UE from the RRC and DU Processor. - virtual async_task remove_ue(ue_index_t ue_index) = 0; -}; - /// Methods to get statistics of the DU processor. class du_processor_statistics_handler { @@ -407,7 +413,7 @@ class du_processor_statistics_handler /// \brief Returns the number of connected UEs at the DU processor /// \return The number of connected UEs. - virtual size_t get_nof_ues() = 0; + virtual size_t get_nof_ues() const = 0; }; class du_processor_interface : public du_processor_f1ap_interface, @@ -419,7 +425,6 @@ class du_processor_interface : public du_processor_f1ap_interface, public du_processor_paging_handler, public du_processor_inactivity_handler, public du_processor_statistics_handler, - public du_processor_ue_handler, public du_processor_mobility_handler { @@ -434,7 +439,6 @@ class du_processor_interface : public du_processor_f1ap_interface, virtual du_processor_paging_handler& get_du_processor_paging_handler() = 0; virtual du_processor_inactivity_handler& get_du_processor_inactivity_handler() = 0; virtual du_processor_statistics_handler& get_du_processor_statistics_handler() = 0; - virtual du_processor_ue_handler& get_du_processor_ue_handler() = 0; virtual du_processor_mobility_handler& get_du_processor_mobility_handler() = 0; virtual du_processor_f1ap_ue_context_notifier& get_du_processor_f1ap_ue_context_notifier() = 0; }; diff --git a/include/srsran/cu_cp/ue_manager.h b/include/srsran/cu_cp/ue_manager.h index b8e74ff313..947f4944b4 100644 --- a/include/srsran/cu_cp/ue_manager.h +++ b/include/srsran/cu_cp/ue_manager.h @@ -38,6 +38,9 @@ class ue_base /// \brief Get the UE index of the UE. virtual ue_index_t get_ue_index() = 0; + + /// \brief Get the UP resource manager of the UE. + virtual up_resource_manager& get_up_resource_manager() = 0; }; /// Interface for DU UE. @@ -46,9 +49,6 @@ class du_ue : public ue_base public: virtual ~du_ue() = default; - /// \brief Get the UP resource manager of the UE. - virtual up_resource_manager& get_up_resource_manager() = 0; - /// \brief Get the task scheduler of the UE. virtual rrc_ue_task_scheduler& get_task_sched() = 0; @@ -109,6 +109,14 @@ class common_ue_manager /// \param[in] pci The PCI of the cell the UE is/was connected to. /// \param[in] c_rnti The RNTI of the UE. virtual ue_index_t get_ue_index(pci_t pci, rnti_t c_rnti) = 0; + + /// \brief Remove the UE context with the given UE index. + /// \param[in] ue_index Index of the UE to be removed. + virtual void remove_ue(ue_index_t ue_index) = 0; + + /// \brief Get the number of UEs. + /// \return Number of UEs. + virtual size_t get_nof_ues() const = 0; }; /// DU Processor UE manager interface. @@ -133,10 +141,6 @@ class du_processor_ue_manager : public common_ue_manager /// \return Pointer to the newly added DU UE if successful, nullptr otherwise. virtual du_ue* add_ue(ue_index_t ue_index, pci_t pci, rnti_t rnti) = 0; - /// \brief Remove the DU UE context with the given UE index. - /// \param[in] ue_index Index of the UE to be removed. - virtual void remove_du_ue(ue_index_t ue_index) = 0; - /// \brief Find the DU UE with the given UE index. /// \param[in] ue_index Index of the UE to be found. /// \return Pointer to the DU UE if found, nullptr otherwise. @@ -161,19 +165,6 @@ class ngap_ue : public ue_base /// \brief Get the DU processor control notifier of the UE. virtual ngap_du_processor_control_notifier& get_du_processor_control_notifier() = 0; - - /// \brief Get the AMF UE ID of the UE. - virtual amf_ue_id_t get_amf_ue_id() = 0; - - /// \brief Get the RAN UE ID of the UE. - virtual ran_ue_id_t get_ran_ue_id() = 0; - - /// \brief Get the aggregate maximum bit rate DL of the UE. - virtual uint64_t get_aggregate_maximum_bit_rate_dl() = 0; - - /// \brief Set the aggregate maximum bit rate DL of the UE. - /// \param[in] aggregate_maximum_bit_rate_dl Aggregate maximum bit rate DL. - virtual void set_aggregate_maximum_bit_rate_dl(uint64_t aggregate_maximum_bit_rate_dl) = 0; }; /// NGAP UE manager interface. @@ -194,10 +185,6 @@ class ngap_ue_manager : public common_ue_manager ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier, ngap_du_processor_control_notifier& du_processor_ctrl_notifier) = 0; - /// \brief Remove the NGAP UE context with the given UE index. - /// \param[in] ue_index Index of the UE to be removed. - virtual void remove_ngap_ue(ue_index_t ue_index) = 0; - /// \brief Find the NGAP UE with the given UE index. /// \param[in] ue_index Index of the UE to be found. /// \return Pointer to the NGAP UE if found, nullptr otherwise. @@ -206,29 +193,6 @@ class ngap_ue_manager : public common_ue_manager /// \brief Get the number of UEs connected to the AMF. /// \return Number of UEs. virtual size_t get_nof_ngap_ues() = 0; - - // Hide -Woverloaded-virtual warning by indicating to the compiler that we want all get_ue_index functions - using common_ue_manager::get_ue_index; - - /// \brief Get the UE index of the UE for the given RAN UE ID. - /// \param[in] ran_ue_id RAN UE ID of the UE. - /// \return Index of the UE if found, invalid index otherwise. - virtual ue_index_t get_ue_index(ran_ue_id_t ran_ue_id) = 0; - - /// \brief Get the UE index of the UE for the given AMF UE ID. - /// \param[in] amf_ue_id AMF UE ID of the UE. - /// \return Index of the UE if found, invalid index otherwise. - virtual ue_index_t get_ue_index(amf_ue_id_t amf_ue_id) = 0; - - /// \brief Set the AMF UE ID of the UE. - /// \param[in] ue_index Index of the UE. - /// \param[in] amf_ue_id The AMF UE ID for the UE. - virtual void set_amf_ue_id(ue_index_t ue_index, amf_ue_id_t amf_ue_id) = 0; - - /// \brief Transfer the NGAP UE context to a new UE e.g. in case of a reestablishment. - /// \param[in] new_ue_index The index of the new UE. - /// \param[in] old_ue_index The index of the old UE. - virtual void transfer_ngap_ue_context(ue_index_t new_ue_index, ue_index_t old_ue_index) = 0; }; } // namespace srs_cu_cp diff --git a/include/srsran/cu_up/cu_up_configuration.h b/include/srsran/cu_up/cu_up_configuration.h index 25e43a53a5..ea48429868 100644 --- a/include/srsran/cu_up/cu_up_configuration.h +++ b/include/srsran/cu_up/cu_up_configuration.h @@ -47,6 +47,9 @@ struct network_interface_config { /// Local port to bind for connection from UPF to receive downlink user-plane traffic (N3 interface). int n3_bind_port = GTPU_PORT; // TS 29.281 Sec. 4.4.2.3 Encapsulated T-PDUs + /// Maximum amount of packets received in a single syscall. + int n3_rx_max_mmsg = 256; + /// Local IP address to bind for connection from DU to receive uplink user-plane traffic. std::string f1u_bind_addr = "127.0.2.1"; @@ -75,6 +78,8 @@ struct cu_up_configuration { unsigned cu_up_id = 0; std::string cu_up_name = "srs_cu_up_01"; std::string plmn = "00101"; ///< Full PLMN as string (without possible filler digit) e.g. "00101" + + std::chrono::seconds statistics_report_period; // CU-UP statistics report period in seconds }; } // namespace srs_cu_up diff --git a/include/srsran/du/du_cell_config.h b/include/srsran/du/du_cell_config.h index 08a800632f..6a5db91546 100644 --- a/include/srsran/du/du_cell_config.h +++ b/include/srsran/du/du_cell_config.h @@ -27,6 +27,7 @@ #include "srsran/ran/carrier_configuration.h" #include "srsran/ran/nr_cgi.h" #include "srsran/ran/pci.h" +#include "srsran/ran/sib/system_info_config.h" #include "srsran/ran/ssb_configuration.h" #include "srsran/ran/tdd/tdd_ul_dl_config.h" #include "srsran/scheduler/config/bwp_configuration.h" @@ -94,15 +95,6 @@ struct pucch_builder_params { pucch_f2_params f2_params; }; -struct cell_selection_info { - /// \brief \c q-RxLevMin, part of \c cellSelectionInfo, \c SIB1, TS 38.311, in dBm. - /// Indicates the required minimum received RSRP level for cell selection/re-selection (see \c Q-RxLevMin, TS 38.311). - bounded_integer q_rx_lev_min = -70; - /// \brief \c q-QualMin, part of \c cellSelectionInfo, \c SIB1, TS 38.311, in dB. - /// Indicates the required minimum received RSRQ level for cell selection/re-selection (see \c Q-QualMin, TS 38.311). - bounded_integer q_qual_min = -20; -}; - /// Parameters that are used to initialize or build the \c PhysicalCellGroupConfig, TS 38.331. struct phy_cell_group_params { /// \brief \c p-NR-FR1, part \c PhysicalCellGroupConfig, TS 38.331. @@ -127,9 +119,6 @@ struct du_cell_config { uint32_t tac; nr_cell_global_id_t nr_cgi; - /// \c cellSelectionInfo, \c SIB1, as per TS 38.311. - cell_selection_info cell_sel_info; - carrier_configuration dl_carrier; carrier_configuration ul_carrier; @@ -151,6 +140,15 @@ struct du_cell_config { /// "intraFreqReselection" as per MIB, TS 38.331. true = allowed; false = notAllowed. bool intra_freq_resel; + /// \c cellSelectionInfo, \c SIB1, as per TS 38.331. + cell_selection_info cell_sel_info; + + /// Content and scheduling information of SI-messages. + optional si_config; + + /// \c ueTimersAndConstants, sent in \c SIB1, as per TS 38.331. + ue_timers_and_constants_config ue_timers_and_constants; + /// Cell-specific DL and UL configuration used by common searchSpaces. dl_config_common dl_cfg_common; ul_config_common ul_cfg_common; diff --git a/include/srsran/du/du_cell_config_helpers.h b/include/srsran/du/du_cell_config_helpers.h index f93fde49ee..feb3cc3777 100644 --- a/include/srsran/du/du_cell_config_helpers.h +++ b/include/srsran/du/du_cell_config_helpers.h @@ -88,16 +88,17 @@ inline du_cell_config make_default_du_cell_config(const cell_config_builder_para cfg.nr_cgi.plmn = "00101"; cfg.nr_cgi.nci = config_helpers::make_nr_cell_identity(411, 32, 1); - cfg.dl_carrier = make_default_dl_carrier_configuration(params); - cfg.ul_carrier = make_default_ul_carrier_configuration(params); - cfg.coreset0_idx = *params.coreset0_index; - cfg.searchspace0_idx = params.search_space0_index; - cfg.dl_cfg_common = make_default_dl_config_common(params); - cfg.ul_cfg_common = make_default_ul_config_common(params); - cfg.scs_common = params.scs_common; - cfg.ssb_cfg = make_default_ssb_config(params); - cfg.cell_barred = false; - cfg.intra_freq_resel = false; + cfg.dl_carrier = make_default_dl_carrier_configuration(params); + cfg.ul_carrier = make_default_ul_carrier_configuration(params); + cfg.coreset0_idx = *params.coreset0_index; + cfg.searchspace0_idx = params.search_space0_index; + cfg.dl_cfg_common = make_default_dl_config_common(params); + cfg.ul_cfg_common = make_default_ul_config_common(params); + cfg.scs_common = params.scs_common; + cfg.ssb_cfg = make_default_ssb_config(params); + cfg.cell_barred = false; + cfg.intra_freq_resel = false; + cfg.ue_timers_and_constants = make_default_ue_timers_and_constants_config(); // The CORESET duration of 3 symbols is only permitted if dmrs-typeA-Position is set to 3. Refer TS 38.211, 7.3.2.2. const pdcch_type0_css_coreset_description coreset0_desc = pdcch_type0_css_coreset_get( diff --git a/include/srsran/du_high/du_high_executor_mapper.h b/include/srsran/du_high/du_high_executor_mapper.h index 2d98c898a4..fff526a454 100644 --- a/include/srsran/du_high/du_high_executor_mapper.h +++ b/include/srsran/du_high/du_high_executor_mapper.h @@ -22,22 +22,34 @@ class du_high_ue_executor_mapper { public: virtual ~du_high_ue_executor_mapper() = default; - /// Method to signal the detection of a new UE and potentially change its executor based on its + /// Method to signal the detection of a new UE and potentially change its executors based on its /// parameters (e.g. PCell). /// \param ue_index Index of the UE. /// \param pcell_index Primary Cell of the new UE. /// \return task executor of this UE. - virtual task_executor& rebind_executor(du_ue_index_t ue_index, du_cell_index_t pcell_index) = 0; + virtual void rebind_executors(du_ue_index_t ue_index, du_cell_index_t pcell_index) = 0; - /// Method to return the executor to which a UE is currently binded. + /// \brief Returns the executor for a given UE that handles control tasks. + /// + /// This executor should be used for tasks that affect the UE state and that we are not too frequent. /// \param ue_index Index of the UE. /// \return task executor of the UE with given UE Index. - virtual task_executor& executor(du_ue_index_t ue_index) = 0; + virtual task_executor& ctrl_executor(du_ue_index_t ue_index) = 0; + + /// \brief Returns the executor currently registered for F1-U Rx PDU handling for a given UE. + /// \param ue_index Index of the UE. + /// \return task executor of the UE with given UE Index. + virtual task_executor& f1u_dl_pdu_executor(du_ue_index_t ue_index) = 0; + + /// Returns the executor currently registered for MAC Rx PDU handling for a given UE. + /// \param ue_index Index of the UE. + /// \return task executor of the UE with given UE Index. + virtual task_executor& mac_ul_pdu_executor(du_ue_index_t ue_index) = 0; /// Method to return the default executor with no associated UE index. /// \param ue_index Index of the UE. /// \return task executor. - task_executor& executor() { return executor(INVALID_DU_UE_INDEX); } + task_executor& executor() { return ctrl_executor(INVALID_DU_UE_INDEX); } }; /// \brief Interface used to access different executors used in the DU-High. diff --git a/include/srsran/e1ap/cu_cp/e1ap_cu_cp.h b/include/srsran/e1ap/cu_cp/e1ap_cu_cp.h index f36e993341..46fc0b6487 100644 --- a/include/srsran/e1ap/cu_cp/e1ap_cu_cp.h +++ b/include/srsran/e1ap/cu_cp/e1ap_cu_cp.h @@ -83,19 +83,15 @@ class e1ap_cu_up_processor_notifier virtual void on_cu_up_e1_setup_request_received(const cu_up_e1_setup_request& msg) = 0; }; -/// Methods used by E1AP to notify the CU-CP. -class e1ap_cu_cp_notifier +/// Handle bearer context removal +class e1ap_bearer_context_removal_handler { public: - virtual ~e1ap_cu_cp_notifier() = default; + virtual ~e1ap_bearer_context_removal_handler() = default; - /// \brief Notifies about the creation of an E1AP. - /// \param[in] bearer_context_manager The E1AP Bearer Context Manager interface. - virtual void on_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager) = 0; - - /// \brief Notifies about the reception of a Bearer Context Inactivity Notification message. - /// \param[in] msg The received Bearer Context Inactivity Notification message. - virtual void on_bearer_context_inactivity_notification_received(const cu_cp_inactivity_notification& msg) = 0; + /// \brief Remove the context of an UE. + /// \param[in] ue_index The index of the UE to remove. + virtual void remove_bearer_context(ue_index_t ue_index) = 0; }; /// Methods used by E1AP to notify the NGAP. @@ -118,12 +114,44 @@ class e1ap_ue_handler virtual void update_ue_context(ue_index_t ue_index, ue_index_t old_ue_index) = 0; }; +/// \brief Interface to query statistics from the E1AP interface. +class e1ap_statistics_handler +{ +public: + virtual ~e1ap_statistics_handler() = default; + + /// \brief Get the number of UEs registered at the E1AP. + /// \return The number of UEs. + virtual size_t get_nof_ues() const = 0; +}; + +/// Methods used by E1AP to notify the CU-CP. +class e1ap_cu_cp_notifier +{ +public: + virtual ~e1ap_cu_cp_notifier() = default; + + /// \brief Notifies about the creation of an E1AP. + /// \param[in] bearer_context_manager The E1AP Bearer Context Manager interface. + /// \param[in] bearer_removal_handler The E1AP bearer context removal handler. + /// \param[in] e1ap_statistic_handler The E1AP statistic interface. + virtual void on_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager, + e1ap_bearer_context_removal_handler& bearer_removal_handler, + e1ap_statistics_handler& e1ap_statistic_handler) = 0; + + /// \brief Notifies about the reception of a Bearer Context Inactivity Notification message. + /// \param[in] msg The received Bearer Context Inactivity Notification message. + virtual void on_bearer_context_inactivity_notification_received(const cu_cp_inactivity_notification& msg) = 0; +}; + /// Combined entry point for E1AP handling. class e1ap_interface : public e1ap_message_handler, public e1ap_event_handler, public e1ap_connection_manager, public e1ap_bearer_context_manager, - public e1ap_ue_handler + public e1ap_ue_handler, + public e1ap_bearer_context_removal_handler, + public e1ap_statistics_handler { public: virtual ~e1ap_interface() = default; diff --git a/include/srsran/e1ap/cu_up/e1ap_cu_up.h b/include/srsran/e1ap/cu_up/e1ap_cu_up.h index 77a363066c..81077e889e 100644 --- a/include/srsran/e1ap/cu_up/e1ap_cu_up.h +++ b/include/srsran/e1ap/cu_up/e1ap_cu_up.h @@ -85,11 +85,23 @@ class e1ap_cu_up_notifier virtual void on_bearer_context_release_command_received(const e1ap_bearer_context_release_command& msg) = 0; }; +/// \brief Interface to query statistics from the E1AP interface. +class e1ap_statistics_handler +{ +public: + virtual ~e1ap_statistics_handler() = default; + + /// \brief Get the number of UEs registered at the E1AP. + /// \return The number of UEs. + virtual size_t get_nof_ues() const = 0; +}; + /// Combined entry point for E1AP handling. class e1ap_interface : public e1ap_message_handler, public e1ap_control_message_handler, public e1ap_event_handler, - public e1ap_connection_manager + public e1ap_connection_manager, + public e1ap_statistics_handler { public: virtual ~e1ap_interface() = default; diff --git a/include/srsran/f1ap/common/f1ap_types.h b/include/srsran/f1ap/common/f1ap_ue_id.h similarity index 95% rename from include/srsran/f1ap/common/f1ap_types.h rename to include/srsran/f1ap/common/f1ap_ue_id.h index 9a31094b3f..5c964aa69c 100644 --- a/include/srsran/f1ap/common/f1ap_types.h +++ b/include/srsran/f1ap/common/f1ap_ue_id.h @@ -46,7 +46,7 @@ constexpr inline gnb_cu_ue_f1ap_id_t int_to_gnb_cu_ue_f1ap_id(uint64_t idx) /// \brief GNB-DU-UE-F1AP-ID used to identify the UE in the F1AP-DU. /// \remark See TS 38.473 Section 9.3.1.5: GNB-DU-UE-F1AP-ID valid values: (0..2^32-1) -constexpr static uint64_t MAX_NOF_DU_F1AP_UES = ((uint64_t)1 << 32); +constexpr static uint64_t MAX_NOF_DU_F1AP_UES = (static_cast(1) << static_cast(32U)); enum class gnb_du_ue_f1ap_id_t : uint64_t { min = 0, max = MAX_NOF_DU_F1AP_UES - 1, invalid = 0x1ffffffff }; constexpr inline uint64_t gnb_du_ue_f1ap_id_to_uint(gnb_du_ue_f1ap_id_t id) diff --git a/include/srsran/f1ap/cu_cp/f1ap_cu.h b/include/srsran/f1ap/cu_cp/f1ap_cu.h index 2f90eac870..1525beaa1b 100644 --- a/include/srsran/f1ap/cu_cp/f1ap_cu.h +++ b/include/srsran/f1ap/cu_cp/f1ap_cu.h @@ -22,7 +22,7 @@ #pragma once -#include "../common/f1ap_types.h" +#include "../common/f1ap_ue_id.h" #include "f1ap_cu_ue_context_update.h" #include "f1ap_interface_management_types.h" #include "srsran/adt/byte_buffer.h" @@ -40,9 +40,8 @@ struct f1ap_ul_rrc_message { }; struct f1ap_dl_rrc_message { - ue_index_t ue_index = ue_index_t::invalid; - ue_index_t old_ue_index = ue_index_t::invalid; - srb_id_t srb_id = srb_id_t::nulltype; + ue_index_t ue_index = ue_index_t::invalid; + srb_id_t srb_id = srb_id_t::nulltype; byte_buffer rrc_container; }; @@ -100,6 +99,12 @@ class f1ap_ue_context_manager /// 'true' in case of a successful outcome, 'false' otherwise. virtual async_task handle_ue_context_modification_request(const f1ap_ue_context_modification_request& request) = 0; + + /// \brief Indicates that a UE changed identifiers (e.g. due to a Reestablishment). + /// \param[in] ue_index New index of the UE. + /// \param[in] old_ue_index Old index of the UE. + /// \return True if both the new and old UE exist, false otherwise. + virtual bool handle_ue_id_update(ue_index_t ue_index, ue_index_t old_ue_index) = 0; }; /// Handle F1AP paging procedures as defined in TS 38.473 section 8.7. @@ -175,6 +180,15 @@ class f1ap_du_processor_notifier virtual du_index_t get_du_index() = 0; }; +class f1ap_task_scheduler +{ +public: + virtual ~f1ap_task_scheduler() = default; + + /// \brief Schedule Async task for a given UE. + virtual void schedule_async_task(ue_index_t ue_index, async_task&& task) = 0; +}; + /// Methods used by F1AP to notify about DU specific events. class f1ap_du_management_notifier { @@ -195,7 +209,29 @@ class f1ap_statistics_handler /// \brief Returns the number of connected UEs at the F1AP /// \return The number of connected UEs. - virtual int get_nof_ues() = 0; + virtual size_t get_nof_ues() const = 0; +}; + +/// Handle UE context removal +class f1ap_ue_context_removal_handler +{ +public: + virtual ~f1ap_ue_context_removal_handler() = default; + + /// \brief Remove the context of an UE. + /// \param[in] ue_index The index of the UE to remove. + virtual void remove_ue_context(ue_index_t ue_index) = 0; +}; + +/// Interface to notify about necessary UE removals. +class f1ap_ue_removal_notifier +{ +public: + virtual ~f1ap_ue_removal_notifier() = default; + + /// \brief Notify the CU-CP to completly remove a UE from the CU-CP. + /// \param[in] ue_index The index of the UE to remove. + virtual void on_ue_removal_required(ue_index_t ue_index) = 0; }; /// Combined entry point for F1AP handling. @@ -205,18 +241,20 @@ class f1ap_cu : public f1ap_message_handler, public f1ap_connection_manager, public f1ap_ue_context_manager, public f1ap_statistics_handler, - public f1ap_paging_manager + public f1ap_paging_manager, + public f1ap_ue_context_removal_handler { public: virtual ~f1ap_cu() = default; - virtual f1ap_message_handler& get_f1ap_message_handler() = 0; - virtual f1ap_event_handler& get_f1ap_event_handler() = 0; - virtual f1ap_rrc_message_handler& get_f1ap_rrc_message_handler() = 0; - virtual f1ap_connection_manager& get_f1ap_connection_manager() = 0; - virtual f1ap_ue_context_manager& get_f1ap_ue_context_manager() = 0; - virtual f1ap_statistics_handler& get_f1ap_statistics_handler() = 0; - virtual f1ap_paging_manager& get_f1ap_paging_manager() = 0; + virtual f1ap_message_handler& get_f1ap_message_handler() = 0; + virtual f1ap_event_handler& get_f1ap_event_handler() = 0; + virtual f1ap_rrc_message_handler& get_f1ap_rrc_message_handler() = 0; + virtual f1ap_connection_manager& get_f1ap_connection_manager() = 0; + virtual f1ap_ue_context_manager& get_f1ap_ue_context_manager() = 0; + virtual f1ap_statistics_handler& get_f1ap_statistics_handler() = 0; + virtual f1ap_paging_manager& get_f1ap_paging_manager() = 0; + virtual f1ap_ue_context_removal_handler& get_f1ap_ue_context_removal_handler() = 0; }; } // namespace srs_cu_cp diff --git a/include/srsran/f1ap/cu_cp/f1ap_cu_factory.h b/include/srsran/f1ap/cu_cp/f1ap_cu_factory.h index 3d3f045b03..ffe2919410 100644 --- a/include/srsran/f1ap/cu_cp/f1ap_cu_factory.h +++ b/include/srsran/f1ap/cu_cp/f1ap_cu_factory.h @@ -33,6 +33,7 @@ namespace srs_cu_cp { std::unique_ptr create_f1ap(f1ap_message_notifier& f1ap_pdu_notifier_, f1ap_du_processor_notifier& f1ap_du_processor_notifier_, f1ap_du_management_notifier& f1ap_du_management_notifier_, + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier_, timer_manager& timers_, task_executor& ctrl_exec_); diff --git a/include/srsran/f1ap/cu_cp/f1ap_cu_ue_context_update.h b/include/srsran/f1ap/cu_cp/f1ap_cu_ue_context_update.h index 785ccf6c84..7af60c423b 100644 --- a/include/srsran/f1ap/cu_cp/f1ap_cu_ue_context_update.h +++ b/include/srsran/f1ap/cu_cp/f1ap_cu_ue_context_update.h @@ -25,7 +25,7 @@ #include "srsran/asn1/f1ap/f1ap_ies.h" #include "srsran/asn1/f1ap/f1ap_pdu_contents_ue.h" #include "srsran/cu_cp/cu_cp_types.h" -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/ran/cause.h" #include "srsran/ran/cu_types.h" #include "srsran/ran/lcid.h" diff --git a/include/srsran/f1ap/du/f1ap_du_ue_config.h b/include/srsran/f1ap/du/f1ap_du_ue_config.h index 63e4969eaa..a455c7ed99 100644 --- a/include/srsran/f1ap/du/f1ap_du_ue_config.h +++ b/include/srsran/f1ap/du/f1ap_du_ue_config.h @@ -23,7 +23,7 @@ #pragma once #include "srsran/adt/optional.h" -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/f1ap/du/f1c_bearer.h" #include "srsran/f1ap/du/f1c_rx_sdu_notifier.h" #include "srsran/ran/du_types.h" @@ -57,8 +57,8 @@ struct f1ap_ue_creation_request { /// \brief Response from the DU F1AP to the request to create a new UE. struct f1ap_ue_creation_response { - bool result; - gnb_du_ue_f1ap_id_t f1ap_ue_id; + bool result = false; + gnb_du_ue_f1ap_id_t f1ap_ue_id = gnb_du_ue_f1ap_id_t::invalid; std::vector f1c_bearers_added; }; diff --git a/include/srsran/fapi_adaptor/phy/messages/pucch.h b/include/srsran/fapi_adaptor/phy/messages/pucch.h index 37c17cf6e8..8772133a3e 100644 --- a/include/srsran/fapi_adaptor/phy/messages/pucch.h +++ b/include/srsran/fapi_adaptor/phy/messages/pucch.h @@ -28,11 +28,13 @@ namespace srsran { namespace fapi_adaptor { -/// Helper function that converts from a PUCCH FAPI PDU to a PUCCH uplink slot PDU. +/// Helper function that converts from a PUCCH FAPI PDU to a PUCCH uplink slot PDU using the system frame number, slot +/// and number of reception antennas. void convert_pucch_fapi_to_phy(uplink_processor::pucch_pdu& pdu, const fapi::ul_pucch_pdu& fapi_pdu, uint16_t sfn, - uint16_t slot); + uint16_t slot, + uint16_t num_rx_ant); } // namespace fapi_adaptor } // namespace srsran diff --git a/include/srsran/fapi_adaptor/precoding_matrix_repository.h b/include/srsran/fapi_adaptor/precoding_matrix_repository.h index 35cac6aeb3..a7f333dfff 100644 --- a/include/srsran/fapi_adaptor/precoding_matrix_repository.h +++ b/include/srsran/fapi_adaptor/precoding_matrix_repository.h @@ -33,7 +33,10 @@ namespace fapi_adaptor { class precoding_matrix_repository { public: - explicit precoding_matrix_repository(std::vector&& repo_) : repo(std::move(repo_)) {} + explicit precoding_matrix_repository(std::vector&& repo_) : repo(std::move(repo_)) + { + srsran_assert(!repo.empty(), "Empty container"); + } /// \brief Returns the precoding matrix associated to the given index. /// diff --git a/include/srsran/fapi_adaptor/uci_part2_correspondence_generator.h b/include/srsran/fapi_adaptor/uci_part2_correspondence_generator.h new file mode 100644 index 0000000000..c77c120c92 --- /dev/null +++ b/include/srsran/fapi_adaptor/uci_part2_correspondence_generator.h @@ -0,0 +1,36 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/fapi_adaptor/uci_part2_correspondence_mapper.h" +#include "srsran/fapi_adaptor/uci_part2_correspondence_repository.h" + +namespace srsran { +namespace fapi_adaptor { + +/// Generates the UCI Part2 correspondence mapper and repository for the given number of CSI-RS resources. +std::pair, std::unique_ptr> +generate_uci_part2_correspondence(unsigned nof_csi_rs_resources); + +} // namespace fapi_adaptor +} // namespace srsran diff --git a/include/srsran/fapi_adaptor/uci_part2_correspondence_mapper.h b/include/srsran/fapi_adaptor/uci_part2_correspondence_mapper.h new file mode 100644 index 0000000000..11c375fbba --- /dev/null +++ b/include/srsran/fapi_adaptor/uci_part2_correspondence_mapper.h @@ -0,0 +1,80 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/span.h" +#include "srsran/adt/static_vector.h" +#include + +namespace srsran { + +struct csi_report_configuration; + +namespace fapi_adaptor { + +/// UCI Part2 information fields that describe the UCI Part 1 correspondence to Part 2 sizes. +struct uci_part2_correspondence_information { + /// Maximum number of parameters. + static constexpr unsigned max_nof_part1_params = 4; + + /// Collects parameter attributes. + struct part1_parameter { + uint16_t offset; + uint8_t bitwidth; + }; + + /// Priority of the Part2 report. + uint16_t priority; + /// Part1 parameters. + static_vector part1_params; + /// Map index into the repository. + uint16_t part2_map_index; + /// Map scope. + uint8_t part2_map_scope; +}; + +/// \brief UCI Part2 correspondence mapper. +/// +/// Maps the given arguments to a a list of UCI Part2 correspondence entries. +class uci_part2_correspondence_mapper +{ +public: + static constexpr unsigned max_nof_part2_entries = 4; + using correspondence_info_container = static_vector; + + explicit uci_part2_correspondence_mapper(std::vector&& correspondence_map_) : + correspondence_map(std::move(correspondence_map_)) + { + srsran_assert(!correspondence_map.empty(), "Empty container"); + } + + /// Maps the given CSI report configuration into a list of correspondence entries. + span map(const csi_report_configuration& csi_report) const; + +private: + /// Each entry contains a list of UCI Part2 entries. + std::vector correspondence_map; +}; + +} // namespace fapi_adaptor +} // namespace srsran diff --git a/include/srsran/fapi_adaptor/uci_part2_correspondence_repository.h b/include/srsran/fapi_adaptor/uci_part2_correspondence_repository.h new file mode 100644 index 0000000000..aa4d1a5abe --- /dev/null +++ b/include/srsran/fapi_adaptor/uci_part2_correspondence_repository.h @@ -0,0 +1,54 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/span.h" +#include "srsran/ran/uci/uci_part2_size_description.h" + +namespace srsran { +namespace fapi_adaptor { + +/// \brief UCI Part2 sizes repository. +/// +/// The repository stores arrays of Part2 sizes as a function of Part1 parameters. +class uci_part2_correspondence_repository +{ + /// Each entry contains a list of UCI Part2 sizes. + std::vector> repo_map; + +public: + explicit uci_part2_correspondence_repository( + std::vector>&& repo_map_) : + repo_map(std::move(repo_map_)) + { + srsran_assert(!repo_map.empty(), "Empty container"); + } + + /// \brief Returns a list of Part2 sizes related to Part1 parameters associated to the given index. + /// + /// \note Index value must be valid. + span get_uci_part2_correspondence(unsigned index) const; +}; + +} // namespace fapi_adaptor +} // namespace srsran diff --git a/include/srsran/gateways/udp_network_gateway.h b/include/srsran/gateways/udp_network_gateway.h index 1605d6f20d..a8a9b14d60 100644 --- a/include/srsran/gateways/udp_network_gateway.h +++ b/include/srsran/gateways/udp_network_gateway.h @@ -30,7 +30,9 @@ struct sockaddr_storage; namespace srsran { -struct udp_network_gateway_config : common_network_gateway_config {}; +struct udp_network_gateway_config : common_network_gateway_config { + unsigned rx_max_mmsg = 256; +}; /// Interface to inject PDUs into gateway entity. class udp_network_gateway_data_handler diff --git a/include/srsran/mac/cell_configuration.h b/include/srsran/mac/cell_configuration.h index 5c9158c514..c9dfc59b0d 100644 --- a/include/srsran/mac/cell_configuration.h +++ b/include/srsran/mac/cell_configuration.h @@ -61,8 +61,8 @@ struct mac_cell_creation_request { bool cell_barred; bool intra_freq_resel; - /// BCCH-DL-SCH Message payload containing the SIB1 to be broadcast. - byte_buffer bcch_dl_sch_payload; + /// BCCH-DL-SCH Message payload containing the SIB1 and SI messages to be broadcast. + std::vector bcch_dl_sch_payloads; // TODO: Fill remaining fields }; diff --git a/include/srsran/mac/mac_ue_configurator.h b/include/srsran/mac/mac_ue_configurator.h index 644135d6a5..a95e3d1758 100644 --- a/include/srsran/mac/mac_ue_configurator.h +++ b/include/srsran/mac/mac_ue_configurator.h @@ -45,7 +45,12 @@ class mac_ue_radio_link_notifier virtual ~mac_ue_radio_link_notifier() = default; /// \brief Notifies that a radio link failure has been detected for a given UE. - virtual SRSRAN_NODISCARD bool on_rlf_detected() = 0; + virtual void on_rlf_detected() = 0; + + /// \brief Notifies that a MAC C-RNTI CE was received with old C-RNTI set to equal to the given UE. + /// + /// The detection of a MAC C-RNTI CE should cancel the handling of any previously detected RLF. + virtual void on_crnti_ce_received() = 0; }; /// Parameters passed to MAC concerning a created logical channel. @@ -76,10 +81,10 @@ struct mac_ue_create_request { /// Outcome of a MAC UE creation request procedure. struct mac_ue_create_response { - du_cell_index_t cell_index; - du_ue_index_t ue_index; + du_cell_index_t cell_index = INVALID_DU_CELL_INDEX; + du_ue_index_t ue_index = INVALID_DU_UE_INDEX; /// C-RNTI allocated to the created UE in the MAC. INVALID_RNTI if the UE was not created. - rnti_t allocated_crnti; + rnti_t allocated_crnti = INVALID_RNTI; }; /// Input parameters used to reconfigure a UE in the scheduler. @@ -130,7 +135,7 @@ class mac_ue_configurator virtual async_task handle_ue_delete_request(const mac_ue_delete_request& cfg) = 0; /// \brief Forward UL-CCCH message to upper layers. - virtual void handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) = 0; + virtual bool handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) = 0; }; } // namespace srsran diff --git a/include/srsran/ngap/ngap.h b/include/srsran/ngap/ngap.h index 1192221f3a..4e5faadd58 100644 --- a/include/srsran/ngap/ngap.h +++ b/include/srsran/ngap/ngap.h @@ -90,6 +90,17 @@ class ngap_connection_manager virtual async_task handle_ng_setup_request(const ng_setup_request& request) = 0; }; +/// Handle ue context removal +class ngap_ue_context_removal_handler +{ +public: + virtual ~ngap_ue_context_removal_handler() = default; + + /// \brief Remove the context of an UE. + /// \param[in] ue_index The index of the UE to remove. + virtual void remove_ue_context(ue_index_t ue_index) = 0; +}; + /// Interface to notify about NGAP connections to the CU-CP class ngap_cu_cp_connection_notifier { @@ -121,34 +132,6 @@ class ngap_cu_cp_du_repository_notifier on_ngap_handover_request(const ngap_handover_request& request) = 0; }; -struct ngap_initial_context_failure_message { - asn1::ngap::cause_c cause; - slotted_id_vector pdu_session_res_failed_to_setup_items; - optional crit_diagnostics; -}; - -struct ngap_initial_context_response_message { - slotted_id_vector pdu_session_res_setup_response_items; - slotted_id_vector pdu_session_res_failed_to_setup_items; - optional crit_diagnostics; -}; - -struct ngap_initial_ue_message { - ue_index_t ue_index = ue_index_t::invalid; - byte_buffer nas_pdu; - asn1::ngap::rrc_establishment_cause_opts establishment_cause; - asn1::ngap::nr_cgi_s nr_cgi; - uint32_t tac; - optional five_g_s_tmsi; -}; - -struct ngap_ul_nas_transport_message { - ue_index_t ue_index = ue_index_t::invalid; - byte_buffer nas_pdu; - asn1::ngap::nr_cgi_s nr_cgi; - uint32_t tac; -}; - /// Handle NGAP NAS Message procedures as defined in TS 38.413 section 8.6. class ngap_nas_message_handler { @@ -157,11 +140,11 @@ class ngap_nas_message_handler /// \brief Initiates Initial UE message procedure as per TS 38.413 section 8.6.1. /// \param[in] msg The initial UE message to transmit. - virtual void handle_initial_ue_message(const ngap_initial_ue_message& msg) = 0; + virtual void handle_initial_ue_message(const cu_cp_initial_ue_message& msg) = 0; /// \brief Initiates Uplink NAS transport procedure as per TS 38.413 section 8.6.3. /// \param[in] msg The ul nas transfer message to transmit. - virtual void handle_ul_nas_transport_message(const ngap_ul_nas_transport_message& msg) = 0; + virtual void handle_ul_nas_transport_message(const cu_cp_ul_nas_transport& msg) = 0; }; class ngap_control_message_handler @@ -200,12 +183,11 @@ class ngap_rrc_ue_control_notifier public: virtual ~ngap_rrc_ue_control_notifier() = default; - /// \brief Notify about the reception of new security capabilities and key. - virtual async_task on_new_security_context(const asn1::ngap::ue_security_cap_s& caps, - const asn1::fixed_bitstring<256, false, true>& key) = 0; + /// \brief Notify about the reception of new security context. + virtual async_task on_new_security_context(const security::security_context& sec_context) = 0; - /// \brief Get required context for inter-gNB handover. - virtual ngap_ue_source_handover_context on_ue_source_handover_context_required() = 0; + /// \brief Get packed handover preparation message for inter-gNB handover. + virtual byte_buffer on_handover_preparation_message_required() = 0; /// \brief Get the status of the security context. virtual bool on_security_enabled() = 0; @@ -259,6 +241,12 @@ class ngap_ue_control_manager ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier, ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier, ngap_du_processor_control_notifier& du_processor_ctrl_notifier) = 0; + + /// \brief Updates the NGAP UE context with a new UE index. + /// \param[in] new_ue_index The new index of the UE. + /// \param[in] old_ue_index The old index of the UE. + /// \returns True if the update was successful, false otherwise. + virtual bool update_ue_index(ue_index_t new_ue_index, ue_index_t old_ue_index) = 0; }; /// \brief Schedules asynchronous tasks associated with an UE. @@ -272,10 +260,10 @@ class ngap_ue_task_scheduler }; /// \brief Interface to query statistics from the NGAP interface. -class ngap_statistic_interface +class ngap_statistics_handler { public: - virtual ~ngap_statistic_interface() = default; + virtual ~ngap_statistics_handler() = default; /// \brief Get the number of UEs registered at the NGAP. /// \return The number of UEs. @@ -289,18 +277,20 @@ class ngap_interface : public ngap_message_handler, public ngap_nas_message_handler, public ngap_control_message_handler, public ngap_ue_control_manager, - public ngap_statistic_interface + public ngap_statistics_handler, + public ngap_ue_context_removal_handler { public: virtual ~ngap_interface() = default; - virtual ngap_message_handler& get_ngap_message_handler() = 0; - virtual ngap_event_handler& get_ngap_event_handler() = 0; - virtual ngap_connection_manager& get_ngap_connection_manager() = 0; - virtual ngap_nas_message_handler& get_ngap_nas_message_handler() = 0; - virtual ngap_control_message_handler& get_ngap_control_message_handler() = 0; - virtual ngap_ue_control_manager& get_ngap_ue_control_manager() = 0; - virtual ngap_statistic_interface& get_ngap_statistic_interface() = 0; + virtual ngap_message_handler& get_ngap_message_handler() = 0; + virtual ngap_event_handler& get_ngap_event_handler() = 0; + virtual ngap_connection_manager& get_ngap_connection_manager() = 0; + virtual ngap_nas_message_handler& get_ngap_nas_message_handler() = 0; + virtual ngap_control_message_handler& get_ngap_control_message_handler() = 0; + virtual ngap_ue_control_manager& get_ngap_ue_control_manager() = 0; + virtual ngap_statistics_handler& get_ngap_statistics_handler() = 0; + virtual ngap_ue_context_removal_handler& get_ngap_ue_context_removal_handler() = 0; }; } // namespace srs_cu_cp diff --git a/include/srsran/ngap/ngap_configuration.h b/include/srsran/ngap/ngap_configuration.h index 7bb5eb8ce8..124c858e78 100644 --- a/include/srsran/ngap/ngap_configuration.h +++ b/include/srsran/ngap/ngap_configuration.h @@ -23,6 +23,7 @@ #pragma once #include "srsran/cu_cp/cu_cp_types.h" +#include #include #include @@ -37,6 +38,7 @@ struct ngap_configuration { std::string plmn; /// Full PLMN as string (without possible filler digit) e.g. "00101" unsigned tac; std::vector slice_configurations; + std::chrono::seconds ue_context_setup_timeout_s; // timeout for ue context setup in seconds }; } // namespace srs_cu_cp diff --git a/include/srsran/ngap/ngap_handover.h b/include/srsran/ngap/ngap_handover.h index 8df3a65587..933fc0fe76 100644 --- a/include/srsran/ngap/ngap_handover.h +++ b/include/srsran/ngap/ngap_handover.h @@ -22,6 +22,7 @@ #pragma once +#include "ngap_types.h" #include "srsran/cu_cp/cu_cp_types.h" #include "srsran/security/security.h" @@ -47,11 +48,6 @@ struct ngap_handover_preparation_response { enum class ngap_handov_type { intra5gs = 0, fivegs_to_eps, eps_to_5gs, fivegs_to_utran }; -struct ngap_ue_aggr_max_bit_rate { - uint64_t ue_aggr_max_bit_rate_dl; - uint64_t ue_aggr_max_bit_rate_ul; -}; - struct ngap_qos_flow_info_item { qos_flow_id_t qos_flow_id = qos_flow_id_t::invalid; optional dl_forwarding; diff --git a/include/srsran/ngap/ngap_init_context_setup.h b/include/srsran/ngap/ngap_init_context_setup.h new file mode 100644 index 0000000000..5503cd98d2 --- /dev/null +++ b/include/srsran/ngap/ngap_init_context_setup.h @@ -0,0 +1,78 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "ngap_types.h" +#include "srsran/cu_cp/cu_cp_types.h" +#include "srsran/ngap/ngap_handover.h" +#include "srsran/ran/crit_diagnostics.h" +#include "srsran/security/security.h" + +namespace srsran { +namespace srs_cu_cp { + +// enum class ngap_handov_type { intra5gs = 0, fivegs_to_eps, eps_to_5gs, fivegs_to_utran }; + +// struct ngap_ue_source_handover_context { +// std::map> pdu_sessions; +// /// Storage for the RRCContainer required in SourceNGRANNode-ToTargetNGRANNode-TransparentContainer (see TS 38.413) +// byte_buffer rrc_container; +// }; + +struct ngap_init_context_setup_request { + ue_index_t ue_index = ue_index_t::invalid; + optional old_amf; + optional ue_aggr_max_bit_rate; + // TODO: Add optional core_network_assist_info_for_inactive + guami_t guami; + optional pdu_session_res_setup_list_cxt_req; + std::vector allowed_nssai; + security::security_context security_context; + // TODO: Add optional trace_activation + // TODO: Add optional mob_restrict_list + optional ue_radio_cap; + optional idx_to_rfsp; + optional masked_imeisv; + optional nas_pdu; + // TODO: Add optional emergency_fallback_ind + // TODO: Add optional rrc_inactive_transition_report_request + optional ue_radio_cap_for_paging; + // TODO: Add optional redirection_voice_fallback + // TODO: Add optional location_report_request_type + // TODO: Add optional cn_assisted_ran_tuning +}; + +struct ngap_init_context_setup_failure { + cause_t cause; + slotted_id_vector pdu_session_res_failed_to_setup_items; + optional crit_diagnostics; +}; + +struct ngap_init_context_setup_response { + slotted_id_vector pdu_session_res_setup_response_items; + slotted_id_vector pdu_session_res_failed_to_setup_items; + optional crit_diagnostics; +}; + +} // namespace srs_cu_cp +} // namespace srsran diff --git a/include/srsran/ngap/ngap_types.h b/include/srsran/ngap/ngap_types.h index e4ec53be6b..6195b5d827 100644 --- a/include/srsran/ngap/ngap_types.h +++ b/include/srsran/ngap/ngap_types.h @@ -22,7 +22,7 @@ #pragma once -#include "srsran/cu_cp/cu_cp_types.h" +#include "srsran/adt/optional.h" #include #include @@ -46,5 +46,18 @@ inline amf_ue_id_t uint_to_amf_ue_id(std::underlying_type_t id) return static_cast(id); } +// Globally unique AMF identifier. +struct guami_t { + std::string plmn; + uint16_t amf_set_id; + uint8_t amf_pointer; + uint8_t amf_region_id; +}; + +struct ngap_ue_aggr_max_bit_rate { + uint64_t ue_aggr_max_bit_rate_dl; + uint64_t ue_aggr_max_bit_rate_ul; +}; + } // namespace srs_cu_cp } // namespace srsran \ No newline at end of file diff --git a/include/srsran/ofh/ofh_factories.h b/include/srsran/ofh/ofh_factories.h index 93c2c0788b..70018f3f1d 100644 --- a/include/srsran/ofh/ofh_factories.h +++ b/include/srsran/ofh/ofh_factories.h @@ -25,11 +25,11 @@ #include "srsran/ofh/ofh_controller.h" #include "srsran/ofh/ofh_ota_symbol_boundary_notifier.h" #include "srsran/ofh/ofh_ota_symbol_handler.h" -#include "srsran/ofh/ofh_receiver_configuration.h" #include "srsran/ofh/ofh_sector.h" #include "srsran/ofh/ofh_sector_config.h" #include "srsran/ofh/ofh_timing_notifier.h" #include "srsran/ofh/ofh_uplane_rx_symbol_notifier.h" +#include "srsran/ofh/receiver/ofh_receiver_configuration.h" #include "srsran/ofh/serdes/ofh_cplane_message_builder.h" #include "srsran/ofh/serdes/ofh_uplane_message_builder.h" #include "srsran/ofh/serdes/ofh_uplane_message_decoder.h" @@ -66,21 +66,21 @@ create_dynamic_compr_method_ofh_user_plane_packet_builder(srslog::basic_logger& /// Creates an Open Fronthaul User-Plane packet decoder which supports static compression method. std::unique_ptr -create_static_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, - subcarrier_spacing scs, - cyclic_prefix cp, - unsigned ru_nof_prbs, - iq_decompressor& decompressor, - const ru_compression_params& compr_params, - const ru_compression_params& prach_compr_params); +create_static_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, + subcarrier_spacing scs, + cyclic_prefix cp, + unsigned ru_nof_prbs, + std::unique_ptr decompressor, + const ru_compression_params& compr_params, + const ru_compression_params& prach_compr_params); /// Creates an Open Fronthaul User-Plane packet decoder which supports dynamic compression method. std::unique_ptr -create_dynamic_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, - subcarrier_spacing scs, - cyclic_prefix cp, - unsigned ru_nof_prbs, - iq_decompressor& decompressor); +create_dynamic_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, + subcarrier_spacing scs, + cyclic_prefix cp, + unsigned ru_nof_prbs, + std::unique_ptr decompressor); /// Open Fronthaul controller config. struct controller_config { @@ -125,9 +125,6 @@ struct symbol_handler_factory_config { ether::eth_frame_pool* frame_pool; }; -/// Creates an Open Fronthaul symbol handler. -std::unique_ptr create_ofh_symbol_handler(symbol_handler_factory_config& config); - /// Creates an Open Fronthaul sector. std::unique_ptr create_ofh_sector(const sector_configuration& sector_cfg); diff --git a/include/srsran/ofh/ofh_sector_config.h b/include/srsran/ofh/ofh_sector_config.h index 8eb7f37a11..49b45fbf37 100644 --- a/include/srsran/ofh/ofh_sector_config.h +++ b/include/srsran/ofh/ofh_sector_config.h @@ -26,8 +26,8 @@ #include "srsran/ofh/compression/compression_params.h" #include "srsran/ofh/ethernet/ethernet_mac_address.h" #include "srsran/ofh/ofh_constants.h" -#include "srsran/ofh/ofh_receiver_configuration.h" #include "srsran/ofh/ofh_uplane_rx_symbol_notifier.h" +#include "srsran/ofh/receiver/ofh_receiver_configuration.h" #include "srsran/ofh/transmitter/ofh_transmitter_configuration.h" #include "srsran/ran/bs_channel_bandwidth.h" #include "srsran/ran/cyclic_prefix.h" diff --git a/include/srsran/ofh/ofh_receiver.h b/include/srsran/ofh/receiver/ofh_receiver.h similarity index 100% rename from include/srsran/ofh/ofh_receiver.h rename to include/srsran/ofh/receiver/ofh_receiver.h diff --git a/include/srsran/ofh/ofh_receiver_configuration.h b/include/srsran/ofh/receiver/ofh_receiver_configuration.h similarity index 72% rename from include/srsran/ofh/ofh_receiver_configuration.h rename to include/srsran/ofh/receiver/ofh_receiver_configuration.h index c52eea470c..cc7f06963e 100644 --- a/include/srsran/ofh/ofh_receiver_configuration.h +++ b/include/srsran/ofh/receiver/ofh_receiver_configuration.h @@ -26,6 +26,7 @@ #include "srsran/ofh/ecpri/ecpri_packet_decoder.h" #include "srsran/ofh/ethernet/vlan_ethernet_frame_decoder.h" #include "srsran/ofh/ofh_constants.h" +#include "srsran/ofh/receiver/ofh_receiver_timing_parameters.h" #include "srsran/ofh/serdes/ofh_uplane_message_decoder.h" #include "srsran/ran/bs_channel_bandwidth.h" #include "srsran/ran/cyclic_prefix.h" @@ -33,30 +34,16 @@ namespace srsran { namespace ofh { -/// \brief Structure storing the reception window timing parameters. -struct du_rx_window_timing_parameters { - /// Offset from the current OTA symbol to the end of UL User-Plane reception window. - std::chrono::microseconds Ta4_max; - /// Offset from the current OTA symbol to the start of UL User-Plane reception window. - std::chrono::microseconds Ta4_min; -}; - /// Open Fronthaul receiver configuration. struct receiver_config { /// Subcarrier spacing. subcarrier_spacing scs; - /// RU bandwidth in PRBs. - unsigned ru_nof_prbs; - /// PRACH Contol-Plane enabled flag. - bool is_prach_cp_enabled; + /// Cyclic prefix. + cyclic_prefix cp; /// PRACH eAxC. static_vector prach_eaxc; /// Uplink eAxC. static_vector ul_eaxc; - /// Uplink static compression header flag. - bool is_uplink_static_compr_hdr_enabled; - /// Cyclic prefix. - cyclic_prefix cp; /// Destination MAC address. ether::mac_address mac_dst_address; /// Source MAC address. @@ -65,6 +52,20 @@ struct receiver_config { uint16_t tci; /// Reception window timing parameters. du_rx_window_timing_parameters rx_timing_params; + /// \brief RU operating bandwidth. + /// + /// Set this option when the operating bandwidth of the RU is larger than the configured bandwidth of the cell. + bs_channel_bandwidth_fr1 ru_operating_bw; + /// Uplink compression parameters. + ofh::ru_compression_params ul_compression_params; + /// PRACH compression parameters. + ofh::ru_compression_params prach_compression_params; + /// Uplink static compression header flag. + bool is_uplink_static_compr_hdr_enabled = true; + /// Enables the Control-Plane PRACH message signalling. + bool is_prach_control_plane_enabled = false; + /// If set to true, the payload size encoded in a eCPRI header is ignored. + bool ignore_ecpri_payload_size_field = false; }; } // namespace ofh diff --git a/include/srsran/ofh/receiver/ofh_receiver_timing_parameters.h b/include/srsran/ofh/receiver/ofh_receiver_timing_parameters.h new file mode 100644 index 0000000000..c5d9fdf894 --- /dev/null +++ b/include/srsran/ofh/receiver/ofh_receiver_timing_parameters.h @@ -0,0 +1,39 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include + +namespace srsran { +namespace ofh { + +/// \brief Structure storing the reception window timing parameters. +struct du_rx_window_timing_parameters { + /// Offset from the current OTA symbol to the end of UL User-Plane reception window. + std::chrono::microseconds Ta4_max; + /// Offset from the current OTA symbol to the start of UL User-Plane reception window. + std::chrono::microseconds Ta4_min; +}; + +} // namespace ofh +} // namespace srsran diff --git a/include/srsran/ofh/serdes/ofh_serdes_factories.h b/include/srsran/ofh/serdes/ofh_serdes_factories.h new file mode 100644 index 0000000000..d254988c93 --- /dev/null +++ b/include/srsran/ofh/serdes/ofh_serdes_factories.h @@ -0,0 +1,69 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/ofh/serdes/ofh_cplane_message_builder.h" +#include "srsran/ofh/serdes/ofh_uplane_message_builder.h" +#include "srsran/ofh/serdes/ofh_uplane_message_decoder.h" +#include + +namespace srsran { +namespace ofh { + +class iq_decompressor; +class iq_compressor; + +/// Creates an Open Fronthaul Control-Plane static compression message builder. +std::unique_ptr create_ofh_control_plane_static_compression_message_builder(); + +/// Creates an Open Fronthaul Control-Plane dynamic compression message builder. +std::unique_ptr create_ofh_control_plane_dynamic_compression_message_builder(); + +/// Creates an Open Fronthaul User-Plane packet builder with static compression header. +std::unique_ptr +create_static_compr_method_ofh_user_plane_packet_builder(srslog::basic_logger& logger, iq_compressor& compressor); + +/// Creates an Open Fronthaul User-Plane packet builder with dynamic compression header. +std::unique_ptr +create_dynamic_compr_method_ofh_user_plane_packet_builder(srslog::basic_logger& logger, iq_compressor& compressor); + +/// Creates an Open Fronthaul User-Plane packet decoder which supports static compression method. +std::unique_ptr +create_static_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, + subcarrier_spacing scs, + cyclic_prefix cp, + unsigned ru_nof_prbs, + std::unique_ptr decompressor, + const ru_compression_params& compr_params, + const ru_compression_params& prach_compr_params); + +/// Creates an Open Fronthaul User-Plane packet decoder which supports dynamic compression method. +std::unique_ptr +create_dynamic_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, + subcarrier_spacing scs, + cyclic_prefix cp, + unsigned ru_nof_prbs, + std::unique_ptr decompressor); + +} // namespace ofh +} // namespace srsran diff --git a/include/srsran/pcap/pcap.h b/include/srsran/pcap/pcap.h index 570328f160..2c49925892 100644 --- a/include/srsran/pcap/pcap.h +++ b/include/srsran/pcap/pcap.h @@ -65,13 +65,15 @@ struct mac_nr_context_info { uint16_t length; }; +enum class mac_pcap_type { udp, dlt }; + /// @brief Interface class for writing a MAC PCAP to a file. class mac_pcap { public: virtual ~mac_pcap() = default; - virtual void open(const std::string& filename_) = 0; + virtual void open(const std::string& filename_, mac_pcap_type type) = 0; virtual void close() = 0; virtual bool is_write_enabled() = 0; virtual void push_pdu(mac_nr_context_info context, const_span pdu) = 0; diff --git a/include/srsran/phy/support/support_factories.h b/include/srsran/phy/support/support_factories.h index e10ae5cd6c..e49acc035e 100644 --- a/include/srsran/phy/support/support_factories.h +++ b/include/srsran/phy/support/support_factories.h @@ -52,7 +52,7 @@ create_resource_grid_factory(std::shared_ptr precoder_ /// Creates a resource grid mapper with an ideal precoding. std::unique_ptr -create_resource_grid_mapper(unsigned nof_ports, unsigned nof_symbols, unsigned nof_subc, resource_grid_writer& writer); +create_resource_grid_mapper(unsigned nof_ports, unsigned nof_subc, resource_grid_writer& writer); /// \brief Creates a generic resource grid pool. /// \param[in] nof_sectors Number of radio sectors. diff --git a/include/srsran/phy/upper/channel_coding/ldpc/ldpc.h b/include/srsran/phy/upper/channel_coding/ldpc/ldpc.h index f9bf006c70..881533ab4c 100644 --- a/include/srsran/phy/upper/channel_coding/ldpc/ldpc.h +++ b/include/srsran/phy/upper/channel_coding/ldpc/ldpc.h @@ -206,6 +206,17 @@ inline units::bits compute_codeblock_size(ldpc_base_graph_type base_graph, unsig return units::bits(base_length * lifting_size); } +/// Computes the codeblock size after the LDPC encoding. +inline units::bits compute_full_codeblock_size(ldpc_base_graph_type base_graph, units::bits codeblock_size) +{ + // BG1 has rate 1/3 and BG2 has rate 1/5. + constexpr unsigned INVERSE_BG1_RATE = 3; + constexpr unsigned INVERSE_BG2_RATE = 5; + unsigned inverse_rate = (base_graph == ldpc_base_graph_type::BG1) ? INVERSE_BG1_RATE : INVERSE_BG2_RATE; + + return codeblock_size * inverse_rate; +} + } // namespace ldpc } // namespace srsran diff --git a/include/srsran/phy/upper/channel_coding/ldpc/ldpc_encoder.h b/include/srsran/phy/upper/channel_coding/ldpc/ldpc_encoder.h index f859c90d08..150f3507da 100644 --- a/include/srsran/phy/upper/channel_coding/ldpc/ldpc_encoder.h +++ b/include/srsran/phy/upper/channel_coding/ldpc/ldpc_encoder.h @@ -40,11 +40,11 @@ class ldpc_encoder /// \brief Encodes a message. /// /// \param[out] output Resulting codeblock. - /// \param[in] input Message: original information bits (can contain filler_bit). + /// \param[in] input Message: original information bits, with the filler bits (if any) set to zero. /// \param[in] cfg Encoder configuration for the current codeblock. /// \note The length of the output codeblock is deduced from the size of parameter \c output. virtual void - encode(span output, span input, const codeblock_metadata::tb_common_metadata& cfg) = 0; + encode(bit_buffer& output, const bit_buffer& input, const codeblock_metadata::tb_common_metadata& cfg) = 0; }; } // namespace srsran diff --git a/include/srsran/phy/upper/channel_coding/ldpc/ldpc_rate_matcher.h b/include/srsran/phy/upper/channel_coding/ldpc/ldpc_rate_matcher.h index 467d34b1a8..25ea504919 100644 --- a/include/srsran/phy/upper/channel_coding/ldpc/ldpc_rate_matcher.h +++ b/include/srsran/phy/upper/channel_coding/ldpc/ldpc_rate_matcher.h @@ -43,7 +43,7 @@ class ldpc_rate_matcher /// single bit. /// \param[in] cfg Configuration parameters. /// \remark The sizes of \c input and \c output determine the behavior of the rate matching algorithm. - virtual void rate_match(bit_buffer& output, span input, const codeblock_metadata& cfg) = 0; + virtual void rate_match(bit_buffer& output, const bit_buffer& input, const codeblock_metadata& cfg) = 0; }; } // namespace srsran diff --git a/include/srsran/phy/upper/channel_modulation/modulation_mapper.h b/include/srsran/phy/upper/channel_modulation/modulation_mapper.h index 759d1c4d09..170fee9a6a 100644 --- a/include/srsran/phy/upper/channel_modulation/modulation_mapper.h +++ b/include/srsran/phy/upper/channel_modulation/modulation_mapper.h @@ -35,6 +35,9 @@ namespace srsran { class modulation_mapper { public: + /// Gets the modulation scaling factor. + static float get_modulation_scaling(modulation_scheme); + /// Default destructor. virtual ~modulation_mapper() = default; diff --git a/include/srsran/phy/upper/channel_processors/channel_processor_factories.h b/include/srsran/phy/upper/channel_processors/channel_processor_factories.h index 517c2cf9f6..6400e363f8 100644 --- a/include/srsran/phy/upper/channel_processors/channel_processor_factories.h +++ b/include/srsran/phy/upper/channel_processors/channel_processor_factories.h @@ -147,7 +147,7 @@ class pdsch_processor_factory virtual ~pdsch_processor_factory() = default; virtual std::unique_ptr create() = 0; virtual std::unique_ptr create_validator() = 0; - std::unique_ptr create(srslog::basic_logger& logger, bool enable_logging_broadcast); + virtual std::unique_ptr create(srslog::basic_logger& logger, bool enable_logging_broadcast); }; std::shared_ptr @@ -156,7 +156,7 @@ create_pdsch_processor_factory_sw(std::shared_ptr std::shared_ptr dmrs_factory); std::shared_ptr -create_pdsch_concurrent_processor_factory_sw(std::shared_ptr segmenter_factory, +create_pdsch_concurrent_processor_factory_sw(std::shared_ptr crc_factory, std::shared_ptr ldpc_enc_factory, std::shared_ptr ldpc_rm_factory, std::shared_ptr prg_factory, @@ -173,6 +173,9 @@ create_pdsch_lite_processor_factory_sw(std::shared_ptr modulator_factory, std::shared_ptr dmrs_factory); +std::shared_ptr create_pdsch_processor_pool(std::shared_ptr, + unsigned max_nof_processors); + class prach_detector_factory { public: @@ -254,11 +257,11 @@ struct pusch_decoder_factory_sw_configuration { std::shared_ptr decoder_factory; std::shared_ptr dematcher_factory; std::shared_ptr segmenter_factory; - unsigned ldpc_decoder_nof_iterations = 10; - bool enable_early_stop = true; + unsigned nof_pusch_decoder_threads = 1; + task_executor* executor = nullptr; }; -std::shared_ptr create_pusch_decoder_factory_sw(pusch_decoder_factory_sw_configuration& config); +std::shared_ptr create_pusch_decoder_factory_sw(pusch_decoder_factory_sw_configuration config); class pusch_demodulator_factory { @@ -280,7 +283,7 @@ class pusch_processor_factory virtual ~pusch_processor_factory() = default; virtual std::unique_ptr create() = 0; virtual std::unique_ptr create_validator() = 0; - std::unique_ptr create(srslog::basic_logger& logger); + virtual std::unique_ptr create(srslog::basic_logger& logger); }; struct pusch_processor_factory_sw_configuration { @@ -298,6 +301,9 @@ struct pusch_processor_factory_sw_configuration { std::shared_ptr create_pusch_processor_factory_sw(pusch_processor_factory_sw_configuration& config); +std::shared_ptr create_pusch_processor_pool(std::shared_ptr factory, + unsigned max_nof_processors); + class ssb_processor_factory { public: diff --git a/include/srsran/phy/upper/channel_processors/channel_processor_formatters.h b/include/srsran/phy/upper/channel_processors/channel_processor_formatters.h index deb5127743..d4d39f2444 100644 --- a/include/srsran/phy/upper/channel_processors/channel_processor_formatters.h +++ b/include/srsran/phy/upper/channel_processors/channel_processor_formatters.h @@ -28,7 +28,9 @@ #include "srsran/phy/upper/channel_processors/pdsch_processor.h" #include "srsran/phy/upper/channel_processors/prach_detector.h" #include "srsran/phy/upper/channel_processors/pucch_processor.h" +#include "srsran/phy/upper/channel_processors/pusch/pusch_decoder_result.h" #include "srsran/phy/upper/channel_processors/pusch/pusch_processor.h" +#include "srsran/phy/upper/channel_processors/pusch/pusch_processor_result_notifier.h" #include "srsran/phy/upper/channel_processors/ssb_processor.h" #include "srsran/ran/pdcch/pdcch_context_formatter.h" #include "srsran/ran/pdsch/pdsch_context_formatter.h" diff --git a/include/srsran/phy/upper/channel_processors/pdsch_processor.h b/include/srsran/phy/upper/channel_processors/pdsch_processor.h index 24b0618712..4326b96c6c 100644 --- a/include/srsran/phy/upper/channel_processors/pdsch_processor.h +++ b/include/srsran/phy/upper/channel_processors/pdsch_processor.h @@ -37,6 +37,14 @@ namespace srsran { class resource_grid_mapper; +class pdsch_processor_notifier +{ +public: + virtual ~pdsch_processor_notifier() = default; + + virtual void on_finish_processing() = 0; +}; + /// Describes the PDSCH processor interface. class pdsch_processor { @@ -150,11 +158,13 @@ class pdsch_processor /// \brief Processes a PDSCH transmission. /// \param[out] mapper Resource grid mapper interface. + /// \param[out] notifier PDSCH processor notifier. /// \param[in] data The codewords to transmit. /// \param[in] pdu Necessary parameters to process the PDSCH transmission. /// \remark The number of transport blocks must be equal to the number of codewords in \c pdu. /// \remark The size of each transport block is determined by data[TB index].size() virtual void process(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier, static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, const pdu_t& pdu) = 0; }; diff --git a/include/srsran/phy/upper/channel_processors/pusch/pusch_decoder.h b/include/srsran/phy/upper/channel_processors/pusch/pusch_decoder.h index db953c76ce..e08ed2fd4b 100644 --- a/include/srsran/phy/upper/channel_processors/pusch/pusch_decoder.h +++ b/include/srsran/phy/upper/channel_processors/pusch/pusch_decoder.h @@ -35,6 +35,7 @@ namespace srsran { class pusch_decoder_buffer; class pusch_decoder_notifier; class rx_softbuffer; +class unique_rx_softbuffer; struct pusch_decoder_result; /// \brief PUSCH decoder interface. @@ -84,7 +85,7 @@ class pusch_decoder /// \param[in] cfg Decoder configuration parameters. /// \return A \ref pusch_decoder_buffer, used to write softbits into the decoder. virtual pusch_decoder_buffer& new_data(span transport_block, - rx_softbuffer& softbuffer, + unique_rx_softbuffer softbuffer, pusch_decoder_notifier& notifier, const configuration& cfg) = 0; }; diff --git a/include/srsran/phy/upper/channel_processors/pusch/pusch_processor.h b/include/srsran/phy/upper/channel_processors/pusch/pusch_processor.h index c3c24f6970..6aefcdd712 100644 --- a/include/srsran/phy/upper/channel_processors/pusch/pusch_processor.h +++ b/include/srsran/phy/upper/channel_processors/pusch/pusch_processor.h @@ -25,7 +25,6 @@ #include "srsran/adt/static_vector.h" #include "srsran/phy/support/re_pattern.h" #include "srsran/phy/upper/channel_coding/ldpc/ldpc.h" -#include "srsran/phy/upper/channel_processors/pusch/pusch_processor_result_notifier.h" #include "srsran/phy/upper/dmrs_mapping.h" #include "srsran/phy/upper/rb_allocation.h" #include "srsran/phy/upper/rx_softbuffer.h" @@ -37,6 +36,10 @@ namespace srsran { +class pusch_processor_result_notifier; +class resource_grid_reader; +class unique_rx_softbuffer; + /// \brief Describes the PUSCH processor interface. /// /// It performs the decoding of PUSCH described in TS38.211 Section 6.3.1. @@ -161,7 +164,7 @@ class pusch_processor /// \param[in] grid Source resource grid. /// \param[in] pdu Necessary parameters to process the PUSCH transmission. virtual void process(span data, - rx_softbuffer& softbuffer, + unique_rx_softbuffer softbuffer, pusch_processor_result_notifier& notifier, const resource_grid_reader& grid, const pdu_t& pdu) = 0; diff --git a/include/srsran/phy/upper/rx_softbuffer_pool.h b/include/srsran/phy/upper/rx_softbuffer_pool.h index 89866eb67e..a44078ecf2 100644 --- a/include/srsran/phy/upper/rx_softbuffer_pool.h +++ b/include/srsran/phy/upper/rx_softbuffer_pool.h @@ -73,6 +73,8 @@ class rx_softbuffer_pool /// The pool does not initialize or modify the contents of the softbuffers. The modules that use the softbuffers are /// responsible for initializing and modifying their contents upon new transmissions. /// + /// It is expected that the pool logs in \c PHY channel the reason of a failed reservation. + /// /// \param[in] slot Indicates the slot context in which the reservation occurs. /// \param[in] id Identifies the softbuffer. /// \param[in] nof_codeblocks Indicates the number of codeblocks to reserve. diff --git a/include/srsran/phy/upper/unique_rx_softbuffer.h b/include/srsran/phy/upper/unique_rx_softbuffer.h index 1995529ba9..056dca5ed7 100644 --- a/include/srsran/phy/upper/unique_rx_softbuffer.h +++ b/include/srsran/phy/upper/unique_rx_softbuffer.h @@ -76,14 +76,38 @@ class unique_rx_softbuffer other.ptr = nullptr; }; + /// Move assigment operator. + unique_rx_softbuffer& operator=(unique_rx_softbuffer&& other) noexcept + { + // Unlock current soft buffer if it is actually not unlocked. + if (ptr != nullptr) { + ptr->unlock(); + } + + // Move the other soft buffer ownership to the current soft buffer. + ptr = other.ptr; + other.ptr = nullptr; + + return *this; + } + /// Gets the softbuffer. rx_softbuffer& get() { srsran_assert(is_valid(), "Invalid softbuffer."); return *ptr; } - rx_softbuffer& operator*() { return get(); } - rx_softbuffer& operator->() { return get(); } + + const rx_softbuffer& get() const + { + srsran_assert(is_valid(), "Invalid softbuffer."); + return *ptr; + } + + rx_softbuffer& operator*() { return get(); } + rx_softbuffer* operator->() { return &get(); } + const rx_softbuffer& operator*() const { return get(); } + const rx_softbuffer* operator->() const { return &get(); } /// Returns true if the unique softbuffer contains a valid softbuffer. bool is_valid() const { return ptr != nullptr; } @@ -91,10 +115,19 @@ class unique_rx_softbuffer /// Unlock and releases the softbuffer resources. void release() { + srsran_assert(ptr != nullptr, "Invalid softbuffer for releasing."); ptr->release(); ptr = nullptr; } + /// Unlocks the softbuffer resources. + void unlock() + { + srsran_assert(ptr != nullptr, "Invalid softbuffer for unlocking."); + ptr->unlock(); + ptr = nullptr; + } + private: /// Underlying pointer to the softbuffer. Set to nullptr for an invalid softbuffer. softbuffer* ptr = nullptr; diff --git a/include/srsran/phy/upper/upper_phy_factories.h b/include/srsran/phy/upper/upper_phy_factories.h index 18bafce208..7a2118e69a 100644 --- a/include/srsran/phy/upper/upper_phy_factories.h +++ b/include/srsran/phy/upper/upper_phy_factories.h @@ -132,9 +132,13 @@ struct pdsch_processor_concurrent_configuration { /// Only used when \ref pdsch_processor_type is set to \c concurrent. Ignored otherwise. /// /// \remark An assertion is triggered if it is not greater than 1. - unsigned nof_pdsch_codeblock_threads; + unsigned nof_pdsch_codeblock_threads = 0; + /// \brief Maximum number of simultaneous active PDSCH transmissions. + /// + /// Sets the maximum number of PDSCH processor instances that can be used simultaneously. + unsigned max_nof_simultaneous_pdsch = 0; /// PDSCH codeblock task executor. Set to \c nullptr if \ref nof_pdsch_threads is less than 2. - task_executor* pdsch_codeblock_task_executor; + task_executor* pdsch_codeblock_task_executor = nullptr; }; /// Lite PDSCH processor configuration parameters. @@ -277,6 +281,10 @@ struct upper_phy_config { unsigned nof_ul_processors; /// Maximum uplink processor thread concurrency. unsigned max_ul_thread_concurrency; + /// Maximum asynchronous PUSCH processing concurrency for each UL processor. + unsigned max_pusch_concurrency; + /// Number of threads that simultaneously use a PUSCH decoder. + unsigned nof_pusch_decoder_threads; /// Number of RBs for downlink. unsigned dl_bw_rb; /// Number of RBs for uplink. @@ -293,10 +301,10 @@ struct upper_phy_config { task_executor* pucch_executor; /// PUSCH task executor. task_executor* pusch_executor; + /// PUSCH decoder task executor. + task_executor* pusch_decoder_executor; /// PRACH task executor. task_executor* prach_executor; - /// PDSCH encoder task executor. Set to \c nullptr to - task_executor* pdsch_encoder_executor; /// Received symbol request notifier. upper_phy_rx_symbol_request_notifier* rx_symbol_request_notifier; }; diff --git a/include/srsran/ran/cause.h b/include/srsran/ran/cause.h index 89cfce9fb9..53d88e9567 100644 --- a/include/srsran/ran/cause.h +++ b/include/srsran/ran/cause.h @@ -99,4 +99,19 @@ enum class cause_misc_t : uint8_t { using cause_t = variant; +// Establishment cause + +enum class establishment_cause_t : uint8_t { + emergency = 0, + high_prio_access, + mt_access, + mo_sig, + mo_data, + mo_voice_call, + mo_video_call, + mo_sms, + mps_prio_access, + mcs_prio_access +}; + } // namespace srsran diff --git a/include/srsran/ran/csi_report/csi_report_on_puxch_utils.h b/include/srsran/ran/csi_report/csi_report_on_puxch_utils.h new file mode 100644 index 0000000000..576258f211 --- /dev/null +++ b/include/srsran/ran/csi_report/csi_report_on_puxch_utils.h @@ -0,0 +1,45 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/ran/csi_report/csi_report_configuration.h" + +namespace srsran { + +/// Gets the number of CSI-RS antenna ports from the PMI codebook type. +inline unsigned csi_report_get_nof_csi_rs_antenna_ports(pmi_codebook_type pmi_codebook) +{ + switch (pmi_codebook) { + case pmi_codebook_type::one: + return 1; + case pmi_codebook_type::two: + return 2; + case pmi_codebook_type::typeI_single_panel_4ports_mode1: + return 4; + case pmi_codebook_type::other: + default: + return 0; + } +} + +} // namespace srsran diff --git a/include/srsran/ran/nr_cgi_helpers.h b/include/srsran/ran/nr_cgi_helpers.h index 6316184786..b88b356dd4 100644 --- a/include/srsran/ran/nr_cgi_helpers.h +++ b/include/srsran/ran/nr_cgi_helpers.h @@ -31,8 +31,9 @@ namespace config_helpers { /// Returns true if the given struct is valid, otherwise false. inline bool is_valid(const nr_cell_global_id_t& cgi) { - // MCC and MNC cannot be null. - if (cgi.mcc == 0 || cgi.mnc == 0 || cgi.plmn.empty() || cgi.plmn_hex.empty()) { + // MCC and MNC cannot be null (init value is 0xf000). + if (cgi.mcc == 0 || cgi.mcc == 0xf000 || cgi.mnc == 0 || cgi.mnc == 0xf000 || cgi.plmn.empty() || + cgi.plmn_hex.empty()) { fmt::print("Invalid MCC, MNC or PLMN configuration.\n"); return false; } diff --git a/include/srsran/ran/prach/rach_config_common.h b/include/srsran/ran/prach/rach_config_common.h index 6976085767..c45f426692 100644 --- a/include/srsran/ran/prach/rach_config_common.h +++ b/include/srsran/ran/prach/rach_config_common.h @@ -45,6 +45,11 @@ struct rach_config_generic { /// \brief \c preambleReceivedTargetPower, part of \c RACH-ConfigGeneric, TS 38.311. /// Target power level at the network receiver side, in dBm. Only values multiple of 2 are valid. bounded_integer preamble_rx_target_pw; + /// Max number of RA preamble transmissions performed before declaring a failure. Values {3, 4, 5, 6, 7, 8, 10, 20, + /// 50, 100, 200}. + uint8_t preamble_trans_max = 7; + /// Power ramping steps for PRACH. Values {0, 2, 4, 6}. + uint8_t power_ramping_step_db = 4; }; /// Used to specify the cell-specific random-access parameters as per TS 38.331, "RACH-ConfigCommon". diff --git a/include/srsran/ran/sib_configuration.h b/include/srsran/ran/sib/sib_configuration.h similarity index 100% rename from include/srsran/ran/sib_configuration.h rename to include/srsran/ran/sib/sib_configuration.h diff --git a/include/srsran/ran/sib/system_info_config.h b/include/srsran/ran/sib/system_info_config.h new file mode 100644 index 0000000000..13147bdd96 --- /dev/null +++ b/include/srsran/ran/sib/system_info_config.h @@ -0,0 +1,242 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/bounded_bitset.h" +#include "srsran/adt/variant.h" +#include + +namespace srsran { + +struct cell_selection_info { + /// \brief \c q-RxLevMin, part of \c cellSelectionInfo, \c SIB1, TS 38.311, in dBm. + /// Indicates the required minimum received RSRP level for cell selection/re-selection (see \c Q-RxLevMin, TS 38.311). + bounded_integer q_rx_lev_min = -70; + /// \brief \c q-QualMin, part of \c cellSelectionInfo, \c SIB1, TS 38.311, in dB. + /// Indicates the required minimum received RSRQ level for cell selection/re-selection (see \c Q-QualMin, TS 38.311). + bounded_integer q_qual_min = -20; +}; + +enum class sib_type { sib1 = 1, sib2 = 2, sib19 = 19, sib_invalid }; + +/// Configures a pattern of SSBs. See TS 38.331, \c SSB-ToMeasure. +/// Equates to longBitmap when size of bitset equals to 64. +/// Equates to mediumBitmap when size of bitset equals to 8. +/// Equates to shortBitmap when size of bitset equals to 4. +/// \remark The size of 64 is chosen for bitset in order account for all the bitmap sizes corresponding to longBitmap, +/// mediumBitmap and shortBitmap. +using ssb_to_measure = bounded_bitset<64>; + +/// Defines configuration for RSSI measurements based on synchronization reference signals. See TS 38.331, \c +/// SS-RSSI-Measurement. +struct ss_rssi_measurement { + /// Indicates the slots in which the UE can perform RSSI measurements. The length of the bitmap is equal to the + /// number of slots in the configured SMTC window. The first (left-most / most significant) bit in the bitmap + /// corresponds to the first slot in the SMTC window, the second bit in the bitmap corresponds to the second slot in + /// the SMTC window, and so on. The UE measures in slots for which the corresponding bit in the bitmap is set to 1. + bounded_bitset<80, true> measurement_slots; + /// Within a slot that is configured for RSSI measurements the UE measures the RSSI from symbol 0 to symbol endSymbol. + /// Values {0,...,3}. + uint8_t end_symbol; +}; + +/// Used to configure measurement timing configurations, i.e., timing occasions at which the UE measures SSBs. See +/// TS 38.331, \c SSB-MTC. +struct ssb_mtc { + /// Periodicity of the measurement window in which to receive SS/PBCH blocks. Values {5, 10, 20, 40, 80, 160} + /// subframes. + uint8_t periodicity_sf; + /// Offset of the measurement window in which to receive SS/PBCH blocks. Values {0,...,periodicity - 1} subframes. + uint8_t offset_sf; + /// Duration of the measurement window in which to receive SS/PBCH blocks. Values {1, 2, 3, 4, 5} subframes. + uint8_t duration_sf; +}; + +enum class t_evaluation { s30, s60, s120, s180, s240, spare3, spare2, spare1 }; + +enum class t_hyst_normal { s30, s60, s120, s180, s240, spare3, spare2, spare1 }; + +struct nr_ns_p_max_value { + /// Used to limit the UE's uplink transmission power on a carrier frequency. See TS 38.101-1. Values {-30,...,33}. + optional additional_p_max; + /// Indicates emission requirements to be fulfilled by the UE. See TS 38.101-1, clause 6.2.3, and TS 38.101-2, + /// clause 6.2.3. Values {0,...,7}. + uint8_t additional_spectrum_emission; +}; + +struct nr_multi_band_info { + /// Provides an NR frequency band number as defined in TS 38.101-1 and TS 38.101-2, table 5.2-1. + /// \remark This field is absent for SIB2 and is mandatory present in SIB4 and frequencyInfoDL-SIB. + optional freq_band_indicator_nr; + /// Maximum of 8 entries. + std::vector nr_ns_p_max_list; +}; + +struct speed_state_reselection_params { + /// The duration for evaluating criteria to enter mobility states. Corresponds to TCRmax in TS 38.304. + t_evaluation t_eval; + /// The additional duration for evaluating criteria to enter normal mobility state. Corresponds to TCRmaxHyst in + /// TS 38.304. + t_hyst_normal t_hyst_norm; + /// The number of cell changes to enter medium mobility state. Corresponds to NCR_M in TS 38.304. Values {1,...,16}. + uint8_t n_cell_change_medium; + /// The number of cell changes to enter high mobility state. Corresponds to NCR_H in TS 38.304. Values {1,...,16}. + uint8_t n_cell_change_high; + /// Additional hysteresis to be applied, in Medium Mobility state respectively, to Qhyst as defined in TS 38.304. + /// Values {-6, -4, -2, 0}. + int q_hyst_sf_medium_db; + /// Additional hysteresis to be applied, in High Mobility state respectively, to Qhyst as defined in TS 38.304. Values + /// {-6, -4, -2, 0}. + int q_hyst_sf_high_db; +}; + +struct sib2_info { + /// Number of SS blocks to average for cell measurement derivation. If the field is absent the UE uses the measurement + /// quantity as specified in TS 38.304. + optional nof_ssbs_to_average; + /// Threshold for consolidation of L1 measurements per RS index. If the \c abs_thres_ss_blocks_consolidation_rsrp, \c + /// abs_thres_ss_blocks_consolidation_rsrq and \c abs_thres_ss_blocks_consolidation_sinr fields are absent, the UE + /// uses the measurement quantity as specified in TS 38.304. + optional abs_thres_ss_blocks_consolidation_rsrp; + optional abs_thres_ss_blocks_consolidation_rsrq; + optional abs_thres_ss_blocks_consolidation_sinr; + /// Used to indicate a cell, beam or measurement object specific offset to be applied when evaluating candidates for + /// cell re-selection. Values {-24,...,24}. + optional q_offset_range_to_best_cell_db; + /// Parameter "Qhyst" in TS 38.304. Values {0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24}. + uint8_t q_hyst_db; + optional speed_state_resel_params; + + /// Parameter "SnonIntraSearchP" in TS 38.304. If this field is not present, the UE applies the (default) value of + /// infinity. Values {0,...,31}. + optional s_non_intra_search_p; + /// Parameter "SnonIntraSearchQ" in TS 38.304. If the field is not present, the UE applies the (default) value of 0 + /// dB. Values {0,...,31}. + optional s_non_intra_search_q; + /// Parameter "ThreshServing, LowP" in TS 38.304. Values {0,...,31}. + uint8_t thresh_serving_low_p; + /// Parameter "ThreshServing, LowQ" in TS 38.304. Values {0,...,31}. + optional thresh_serving_low_q; + /// Defines the absolute priority of the concerned carrier frequency, as used by the cell reselection procedure. Value + /// 0 means: lowest priority. Values {0,...,7}. + uint8_t cell_reselection_priority; + /// Indicates a fractional value to be added to the value of \c cell_reselection_priority to obtain the absolute + /// priority of the concerned carrier frequency for E-UTRA and NR.Values {0.2, 0.4, 0.6, 0.8}. + optional cell_reselection_sub_priority; + + /// Parameter "Qrxlevmin" in TS 38.304, applicable for intra-frequency neighbour cells. Values {-70,...,-22}. + int8_t q_rx_lev_min; + /// Parameter "QrxlevminSUL" in TS 38.304, applicable for intra-frequency neighbour cells. Values {-70,...,-22}. + optional q_rx_lev_min_sul; + /// Parameter "Qqualmin" in TS 38.304, applicable for intra-frequency neighbour cells. If the field is not + /// present, the UE applies the (default) value of negative infinity. Value {-43,...,-12}. + optional q_qual_min; + /// Parameter "SIntraSearchP" in TS 38.304. If this field is not present, the UE applies the (default) value of + /// infinity. Values {0,...,31}. + uint8_t s_intra_search_p; + /// Parameter "SIntraSearchQ2 in TS 38.304. If the field is not present, the UE applies the (default) value of 0 + /// dB. Values {0,...,31}. + optional s_intra_search_q; + /// Parameter "TreselectionNR" in TS 38.304. Values {0,...,7}. + uint8_t t_reselection_nr; + /// Indicates the list of frequency bands for which the NR cell reselection parameters apply. Maximum of 8 entries. + std::vector freq_band_lst; + /// Indicates the list of frequency bands for which the NR cell reselection parameters apply. Maximum of 8 entries. + std::vector freq_band_lst_sul; + /// Used to limit the UE's uplink transmission power on a carrier frequency. See TS 38.101-1. Values {-30,...,33}. + optional p_max; + optional smtc; + optional ss_rssi_meas; + optional ssb_to_meas; + /// This field indicates whether the UE can utilize serving cell timing to derive the index of SS block transmitted by + /// neighbour cell. + bool derive_ssb_index_from_cell; +}; + +struct sib19_info { + optional distance_thres; + // TODO +}; + +/// \brief Variant type that can hold different types of SIBs that go in a SI message. +using sib_info = variant; + +inline sib_type get_sib_info_type(const sib_info& sib) +{ + if (variant_holds_alternative(sib)) { + return sib_type::sib2; + } + if (variant_holds_alternative(sib)) { + return sib_type::sib19; + } + return sib_type::sib_invalid; +} + +/// \brief This struct contains the information required for the scheduling of the SI messages by the network. +struct si_message_sched_info { + /// List of SIBs (sib2, sib3, ...) included in this SI message. The list has at most 32 elements. + std::vector sib_mapping_info; + /// Periodicity of the SI-message in radio frames. Values: {8, 16, 32, 64, 128, 256, 512}. + unsigned si_period_radio_frames = 32; +}; + +/// This struct contains the information required for the generation of the SI messages sent by the network and the +/// generation of the SIB1 "SI-SchedulingInfo" field of the SIB1. See TS 38.331, "SystemInformation" and +/// "SI-SchedulingInfo". +struct si_scheduling_info_config { + /// The length of the SI scheduling window, in slots. It is always shorter or equal to the period of the SI message. + /// Values: {5, 10, 20, 40, 80, 160, 320, 640, 1280}. + unsigned si_window_len_slots; + /// List of SI-messages and associated scheduling information. + std::vector si_sched_info; + /// Information included in each SIB that is scheduled as part of one of the SI-messages. + std::vector sibs; +}; + +/// This struct contains the information required for the generation of the SIB1 "UE-TimersAndConstants" field of the +/// SIB1. See TS 38.331 section 7. +struct ue_timers_and_constants_config { + /// t300 + /// Values (in ms): {100, 200, 300, 400, 600, 1000, 1500, 2000} + std::chrono::milliseconds t300; + /// t301 + /// Values (in ms): {100, 200, 300, 400, 600, 1000, 1500, 2000} + std::chrono::milliseconds t301; + /// \brief Timer triggered by UE upon detection of N310 consecutive out-of-sync indications from lower layers, as + /// per TS 38.331, 7.1.1. Values: {ms0, ms50, ms100, ms200, ms500, ms1000, ms2000}. + std::chrono::milliseconds t310; + /// n310 + /// Values: {1, 2, 3, 4, 6, 8, 10, 20} + unsigned n310; + /// \brief Timer triggered by UE upon initiating RRC connection reestablishment procedure, as per TS 38.331 7.1.1. + /// Values: {ms1000, ms3000, ms5000, ms10000, ms15000, ms20000, ms30000}. + std::chrono::milliseconds t311; + /// n311 + /// Values: {1, 2, 3, 4, 5, 6, 8, 10} + unsigned n311; + /// t319 + /// Values (in ms): {100, 200, 300, 400, 600, 1000, 1500, 2000} + std::chrono::milliseconds t319; +}; + +} // namespace srsran diff --git a/include/srsran/ran/slot_pdu_capacity_constants.h b/include/srsran/ran/slot_pdu_capacity_constants.h index 72fd9c39d9..52066036d6 100644 --- a/include/srsran/ran/slot_pdu_capacity_constants.h +++ b/include/srsran/ran/slot_pdu_capacity_constants.h @@ -57,7 +57,7 @@ static constexpr size_t MAX_DL_PDCCH_PDUS_PER_SLOT = MAX_SIB1_PDUS_PER_SLOT + MAX_RAR_PDUS_PER_SLOT + MAX_UE_PDUS_PER_SLOT + MAX_PAGING_PDUS_PER_SLOT; /// [Implementation defined] This corresponds to maximum number of PDSCH PDUs that can be scheduled per slot. -static constexpr size_t MAX_DL_PDSCH_PDUS_PER_SLOT = +static constexpr size_t MAX_PDSCH_PDUS_PER_SLOT = MAX_SIB1_PDUS_PER_SLOT + MAX_RAR_PDUS_PER_SLOT + MAX_UE_PDUS_PER_SLOT + MAX_PAGING_PDUS_PER_SLOT; /// [Implementation defined] Maximum number of PRACH occasions per slot supported by the current implementation. @@ -92,7 +92,7 @@ static constexpr size_t MAX_UL_PDUS_PER_SLOT = MAX_PRACH_OCCASIONS_PER_SLOT + MAX_PUCCH_PDUS_PER_SLOT + MAX_PUSCH_PDUS_PER_SLOT; /// [Implementation defined] Maximum number of UCI PDUS per UCI indication. -static constexpr size_t MAX_UCI_PDUS_PER_UCI_IND = 8; +static constexpr size_t MAX_UCI_PDUS_PER_UCI_IND = MAX_PUCCH_PDUS_PER_SLOT; /// [Implementation defined] Maximum number of HARQ VALUES per HARQ PDU. static constexpr size_t MAX_HARQ_VALUES_PER_HARQ_PDU = 8; diff --git a/include/srsran/rlc/rlc_config.h b/include/srsran/rlc/rlc_config.h index ace8f28986..8cde9ee0a9 100644 --- a/include/srsran/rlc/rlc_config.h +++ b/include/srsran/rlc/rlc_config.h @@ -166,6 +166,9 @@ struct rlc_tx_am_config { uint32_t max_retx_thresh; ///< Max number of retx int32_t poll_pdu; ///< Insert poll bit after this many PDUs int32_t poll_byte; ///< Insert poll bit after this much data (bytes) + + // Custom non-standard parameters + uint32_t max_window; ///< Custom parameter to limit the maximum window size for memory reasons. 0 means no limit. }; /// \brief Configurable parameters for RLC AM @@ -361,12 +364,13 @@ struct formatter { auto format(srsran::rlc_tx_am_config cfg, FormatContext& ctx) -> decltype(std::declval().out()) { return format_to(ctx.out(), - "tx_sn_size={} t_poll_retx={} max_retx={} poll_pdu={} poll_byte={}", + "tx_sn_size={} t_poll_retx={} max_retx={} poll_pdu={} poll_byte={} max_window={}", cfg.sn_field_length, cfg.t_poll_retx, cfg.max_retx_thresh, cfg.poll_pdu, - cfg.poll_byte); + cfg.poll_byte, + cfg.max_window); } }; diff --git a/include/srsran/rlc/rlc_metrics.h b/include/srsran/rlc/rlc_metrics.h index 7b70fbdcf1..0f6ab1d593 100644 --- a/include/srsran/rlc/rlc_metrics.h +++ b/include/srsran/rlc/rlc_metrics.h @@ -35,6 +35,7 @@ struct rlc_metrics { rb_id_t rb_id; rlc_tx_metrics tx; rlc_rx_metrics rx; + unsigned counter; }; /// \brief Notifier interface used to report RLC metrics. diff --git a/include/srsran/rrc/rrc.h b/include/srsran/rrc/rrc.h index 9554ca42a6..40b59b3c21 100644 --- a/include/srsran/rrc/rrc.h +++ b/include/srsran/rrc/rrc.h @@ -72,10 +72,6 @@ class rrc_ul_dcch_pdu_handler virtual void handle_ul_dcch_pdu(const srb_id_t srb_id, byte_buffer pdu) = 0; }; -struct dl_nas_transport_message { - byte_buffer nas_pdu; -}; - /// This interface represents the data entry point for the RRC receiving NAS PDUs. /// The higher-layers will use this class to pass PDUs into the RRC. class rrc_dl_nas_message_handler @@ -84,8 +80,8 @@ class rrc_dl_nas_message_handler virtual ~rrc_dl_nas_message_handler() = default; /// \brief Handle the received Downlink NAS Transport message. - /// \param[in] msg The Downlink NAS Transport message. - virtual void handle_dl_nas_transport_message(const dl_nas_transport_message& msg) = 0; + /// \param[in] nas_pdu The received NAS PDU. + virtual void handle_dl_nas_transport_message(byte_buffer nas_pdu) = 0; }; } // namespace srs_cu_cp diff --git a/include/srsran/rrc/rrc_du.h b/include/srsran/rrc/rrc_du.h index 2be728549f..a029bb781a 100644 --- a/include/srsran/rrc/rrc_du.h +++ b/include/srsran/rrc/rrc_du.h @@ -77,10 +77,6 @@ class rrc_du_ue_repository : public rrc_amf_connection_handler /// Creates a new RRC UE object and returns a handle to it. virtual rrc_ue_interface* add_ue(up_resource_manager& resource_mng, const rrc_ue_creation_message msg) = 0; - /// Remove a RRC UE object. - /// \param[in] ue_index The index of the UE object to remove. - virtual void remove_ue(ue_index_t ue_index) = 0; - /// Get a RRC UE object. virtual rrc_ue_interface* find_ue(ue_index_t ue_index) = 0; @@ -88,15 +84,43 @@ class rrc_du_ue_repository : public rrc_amf_connection_handler virtual void release_ues() = 0; }; +/// Handle RRC UE removal +class rrc_ue_removal_handler +{ +public: + virtual ~rrc_ue_removal_handler() = default; + + /// Remove a RRC UE object. + /// \param[in] ue_index The index of the UE object to remove. + virtual void remove_ue(ue_index_t ue_index) = 0; +}; + +/// \brief Interface to query statistics from the RRC DU interface. +class rrc_du_statistics_handler +{ +public: + virtual ~rrc_du_statistics_handler() = default; + + /// \brief Get the number of UEs registered at the RRC DU. + /// \return The number of UEs. + virtual size_t get_nof_ues() const = 0; +}; + /// Combined entry point for the RRC DU handling. -class rrc_du_interface : public rrc_du_cell_manager, public rrc_du_ue_manager, public rrc_du_ue_repository +class rrc_du_interface : public rrc_du_cell_manager, + public rrc_du_ue_manager, + public rrc_du_ue_repository, + public rrc_ue_removal_handler, + public rrc_du_statistics_handler { public: virtual ~rrc_du_interface() = default; - virtual rrc_du_cell_manager& get_rrc_du_cell_manager() = 0; - virtual rrc_du_ue_manager& get_rrc_du_ue_manager() = 0; - virtual rrc_du_ue_repository& get_rrc_du_ue_repository() = 0; + virtual rrc_du_cell_manager& get_rrc_du_cell_manager() = 0; + virtual rrc_du_ue_manager& get_rrc_du_ue_manager() = 0; + virtual rrc_du_ue_repository& get_rrc_du_ue_repository() = 0; + virtual rrc_ue_removal_handler& get_rrc_ue_removal_handler() = 0; + virtual rrc_du_statistics_handler& get_rrc_du_statistics_handler() = 0; }; } // namespace srs_cu_cp diff --git a/include/srsran/rrc/rrc_ue.h b/include/srsran/rrc/rrc_ue.h index 0033bcb585..fd9f1066e1 100644 --- a/include/srsran/rrc/rrc_ue.h +++ b/include/srsran/rrc/rrc_ue.h @@ -49,12 +49,10 @@ class rrc_pdu_f1ap_notifier /// \brief Notify the PDCP about a new RRC PDU that needs ciphering and integrity protection. /// \param[in] pdu The RRC PDU. /// \param[in] srb_id The SRB ID of the PDU. - /// \param[in] old_ue_index Optional old index of UE, e.g. for reestablishment. - virtual void - on_new_rrc_pdu(const srb_id_t srb_id, const byte_buffer& pdu, ue_index_t old_ue_index = ue_index_t::invalid) = 0; + virtual void on_new_rrc_pdu(const srb_id_t srb_id, const byte_buffer& pdu) = 0; }; -/// Interface used by the RRC Setup procedure to notifiy the RRC UE. +/// Interface used by the RRC Setup procedure to notify the RRC UE. class rrc_ue_setup_proc_notifier { public: @@ -65,8 +63,8 @@ class rrc_ue_setup_proc_notifier /// \param[in] dl_ccch_msg The DL CCCH message. virtual void on_new_dl_ccch(const asn1::rrc_nr::dl_ccch_msg_s& dl_ccch_msg) = 0; - /// \brief Notify about the need to delete a UE. - virtual void on_ue_delete_request(const cause_t& cause) = 0; + /// \brief Notify about the need to release a UE. + virtual void on_ue_release_required(const cause_t& cause) = 0; }; struct srb_creation_message { @@ -103,8 +101,8 @@ class rrc_ue_reconfiguration_proc_notifier /// \param[in] dl_dcch_msg The DL DCCH message. virtual void on_new_dl_dcch(srb_id_t srb_id, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg) = 0; - /// \brief Notify about the need to delete a UE. - virtual void on_ue_delete_request(const cause_t& cause) = 0; + /// \brief Notify about the need to release a UE. + virtual void on_ue_release_required(const cause_t& cause) = 0; }; /// Interface used by the RRC security mode procedure @@ -119,9 +117,6 @@ class rrc_ue_security_mode_command_proc_notifier /// \param[in] dl_dcch_msg The DL DCCH message. virtual void on_new_dl_dcch(srb_id_t srb_id, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg) = 0; - /// \brief Notify about the need to delete a UE. - virtual void on_ue_delete_request(const cause_t& cause) = 0; - /// \brief Setup AS security in the UE. This includes configuring /// the PDCP entity security on SRB1 with the new AS keys. virtual void on_new_as_security_context() = 0; @@ -139,25 +134,9 @@ class rrc_ue_reestablishment_proc_notifier /// \param[in] dl_dcch_msg The DL DCCH message. virtual void on_new_dl_dcch(srb_id_t srb_id, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg) = 0; - /// \brief Notify about a DL DCCH message. - /// \param[in] dl_dcch_msg The DL DCCH message. - /// \param[in] ue_index The old index of the UE. - virtual void - on_new_dl_dcch(srb_id_t srb_id, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg, ue_index_t old_ue_index) = 0; - /// \brief Refresh AS security keys after horizontal key derivation. /// This includes configuring the PDCP entity security on SRB1 with the new AS keys. virtual void on_new_as_security_context() = 0; - - /// \brief Notify about the need to delete a UE. - virtual void on_ue_delete_request(const cause_t& cause) = 0; -}; - -struct rrc_ue_context_release_command { - ue_index_t ue_index = ue_index_t::invalid; - cause_t cause; - byte_buffer rrc_release_pdu; - optional srb_id; }; /// Interface to notify about RRC UE Context messages. @@ -169,7 +148,7 @@ class rrc_ue_du_processor_notifier /// \brief Notify about a UE Context Release Command. /// \param[in] cmd The UE Context Release Command. virtual async_task - on_ue_context_release_command(const rrc_ue_context_release_command& cmd) = 0; + on_ue_context_release_command(const cu_cp_ue_context_release_command& cmd) = 0; /// \brief Notify about a required reestablishment context modification. /// \param[in] ue_index The index of the UE that needs the context modification. @@ -186,20 +165,6 @@ class rrc_ue_task_scheduler virtual timer_factory get_timer_factory() = 0; }; -struct initial_ue_message { - ue_index_t ue_index = ue_index_t::invalid; - byte_buffer nas_pdu; - rrc_cell_context cell; - asn1::rrc_nr::establishment_cause_opts establishment_cause; - optional five_g_s_tmsi; -}; - -struct ul_nas_transport_message { - ue_index_t ue_index = ue_index_t::invalid; - byte_buffer nas_pdu; - rrc_cell_context cell; -}; - /// Interface to notify about NAS messages. class rrc_ue_nas_notifier { @@ -208,11 +173,11 @@ class rrc_ue_nas_notifier /// \brief Notify about the Initial UE Message. /// \param[in] msg The initial UE message. - virtual void on_initial_ue_message(const initial_ue_message& msg) = 0; + virtual void on_initial_ue_message(const cu_cp_initial_ue_message& msg) = 0; /// \brief Notify about an Uplink NAS Transport message. /// \param[in] msg The Uplink NAS Transport message. - virtual void on_ul_nas_transport_message(const ul_nas_transport_message& msg) = 0; + virtual void on_ul_nas_transport_message(const cu_cp_ul_nas_transport& msg) = 0; }; struct rrc_reconfiguration_response_message { @@ -290,9 +255,9 @@ class rrc_ue_init_security_context_handler public: virtual ~rrc_ue_init_security_context_handler() = default; - /// \brief Handle the received Downlink NAS Transport message. - /// \param[in] msg The Downlink NAS Transport message. - virtual async_task handle_init_security_context(const security::security_context& msg) = 0; + /// \brief Handle the received Init Security Context. + /// \param[in] sec_ctxt The Init Security Context. + virtual async_task handle_init_security_context(const security::security_context& sec_ctxt) = 0; /// \brief Get the status of the security context. virtual bool get_security_enabled() = 0; @@ -338,7 +303,11 @@ class rrc_ue_reestablishment_notifier /// \brief Notify the CU-CP to transfer and remove ue contexts. /// \param[in] ue_index The new UE index of the UE that sent the Reestablishment Request. /// \param[in] old_ue_index The old UE index of the UE that sent the Reestablishment Request. - virtual void on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) = 0; + virtual async_task on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) = 0; + + /// \brief Notify the CU-CP to completly remove a UE from the CU-CP. + /// \param[in] ue_index The index of the UE to remove. + virtual void on_ue_removal_required(ue_index_t ue_index) = 0; }; class rrc_ue_context_handler diff --git a/include/srsran/scheduler/config/scheduler_expert_config.h b/include/srsran/scheduler/config/scheduler_expert_config.h index 4a92aa0d71..ece2e5f9d7 100644 --- a/include/srsran/scheduler/config/scheduler_expert_config.h +++ b/include/srsran/scheduler/config/scheduler_expert_config.h @@ -32,7 +32,8 @@ #include "srsran/ran/pdsch/pdsch_mcs.h" #include "srsran/ran/resource_block.h" #include "srsran/ran/sch_mcs.h" -#include "srsran/ran/sib_configuration.h" +#include "srsran/ran/sib/sib_configuration.h" +#include "srsran/ran/slot_pdu_capacity_constants.h" namespace srsran { @@ -57,7 +58,7 @@ struct scheduler_ue_expert_config { /// Set boundaries, in number of RBs, for UE PDSCH grants. interval pdsch_nof_rbs{1, MAX_NOF_PRBS}; /// Measurements periodicity in nof. slots over which the new Timing Advance Command is computed. - unsigned ta_measurement_slot_period; + unsigned ta_measurement_slot_period{80}; /// Timing Advance Command (T_A) offset threshold above which Timing Advance Command is triggered. Possible valid /// values {0,...,32}. If set to less than zero, issuing of TA Command is disabled. int8_t ta_cmd_offset_threshold; @@ -66,6 +67,12 @@ struct scheduler_ue_expert_config { /// Direct Current (DC) offset, in number of subcarriers, used in PUSCH, by default. The gNB may supersede this DC /// offset value through RRC messaging. See TS38.331 - "txDirectCurrentLocation". dc_offset_t initial_ul_dc_offset{dc_offset_t::center}; + /// Maximum number of PDSCH grants per slot. + unsigned max_pdschs_per_slot = MAX_PDSCH_PDUS_PER_SLOT; + /// Maximum number of PUSCH grants per slot. + unsigned max_puschs_per_slot = MAX_PUSCH_PDUS_PER_SLOT; + /// Maximum number of PDCCH grant allocation attempts per slot. Default: Unlimited. + unsigned max_pdcch_alloc_attempts_per_slot = std::max(MAX_DL_PDCCH_PDUS_PER_SLOT, MAX_UL_PDCCH_PDUS_PER_SLOT); /// CQI offset increment used in outer loop link adaptation (OLLA) algorithm. If set to zero, OLLA is disabled. float olla_cqi_inc{0.001}; /// DL Target BLER to be achieved with OLLA. @@ -78,6 +85,10 @@ struct scheduler_ue_expert_config { float olla_ul_target_bler{0.01}; /// Maximum UL SNR offset that the OLLA algorithm can apply on top of the estimated UL SINR. float olla_max_ul_snr_offset{5.0}; + /// Threshold for drop in CQI of the first HARQ transmission above which HARQ retransmissions are cancelled. + uint8_t dl_harq_la_cqi_drop_threshold{2}; + /// Threshold for drop in nof. layers of the first HARQ transmission above which HARQ retransmission is cancelled. + uint8_t dl_harq_la_ri_drop_threshold{1}; }; /// \brief System Information scheduling statically configurable expert parameters. diff --git a/include/srsran/scheduler/config/serving_cell_config_factory.h b/include/srsran/scheduler/config/serving_cell_config_factory.h index bcd8be5cf3..d5ce3726e2 100644 --- a/include/srsran/scheduler/config/serving_cell_config_factory.h +++ b/include/srsran/scheduler/config/serving_cell_config_factory.h @@ -26,6 +26,7 @@ #include "serving_cell_config.h" #include "srsran/ran/csi_rs/csi_meas_config.h" #include "srsran/ran/pdcch/aggregation_level.h" +#include "srsran/ran/sib/system_info_config.h" #include "srsran/ran/tdd/tdd_ul_dl_config.h" namespace srsran { @@ -109,5 +110,8 @@ make_pdsch_time_domain_resource(uint8_t ss0_idx, optional ded_pdcch_cfg = {}, optional tdd_cfg = {}); +/// \brief Creates a default UE timers and constants configuration. +ue_timers_and_constants_config make_default_ue_timers_and_constants_config(); + } // namespace config_helpers } // namespace srsran diff --git a/include/srsran/scheduler/config/si_scheduling_config.h b/include/srsran/scheduler/config/si_scheduling_config.h new file mode 100644 index 0000000000..389b15ddbc --- /dev/null +++ b/include/srsran/scheduler/config/si_scheduling_config.h @@ -0,0 +1,52 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/support/units.h" + +namespace srsran { + +/// \brief Maximum number of SI messages that can be scheduled as per TS 38.331, "maxSI-Message". +const size_t MAX_SI_MESSAGES = 32; + +/// Scheduling parameters of the SI message. +struct si_message_scheduling_config { + /// SI message payload size in bytes. + units::bytes msg_len; + /// Periodicity of the SI-message in radio frames. Values: {8, 16, 32, 64, 128, 256, 512}. + unsigned period_radio_frames; +}; + +/// \brief Configuration of the SI message scheduling. +/// +/// This struct will be handled by the MAC scheduler to determine the required PDCCH and PDSCH grants for SI. +struct si_scheduling_config { + /// List of SI-messages to schedule. + std::vector si_messages; + /// \brief The length of the SI scheduling window, in slots. + /// + /// It is always shorter or equal to the period of the SI message. Values: {5, 10, 20, 40, 80, 160, 320, 640, 1280}. + unsigned si_window_len_slots; +}; + +} // namespace srsran diff --git a/include/srsran/scheduler/scheduler_configurator.h b/include/srsran/scheduler/scheduler_configurator.h index c8382cfc88..03d7db86c0 100644 --- a/include/srsran/scheduler/scheduler_configurator.h +++ b/include/srsran/scheduler/scheduler_configurator.h @@ -31,7 +31,7 @@ #include "srsran/ran/phy_time_unit.h" #include "srsran/ran/prach/prach_constants.h" #include "srsran/ran/rnti.h" -#include "srsran/ran/sib_configuration.h" +#include "srsran/ran/sib/sib_configuration.h" #include "srsran/ran/slot_pdu_capacity_constants.h" #include "srsran/ran/slot_point.h" #include "srsran/ran/sr_configuration.h" @@ -42,6 +42,7 @@ #include "srsran/scheduler/config/dmrs.h" #include "srsran/scheduler/config/logical_channel_config.h" #include "srsran/scheduler/config/serving_cell_config.h" +#include "srsran/scheduler/config/si_scheduling_config.h" #include "srsran/scheduler/scheduler_dci.h" namespace srsran { @@ -86,6 +87,9 @@ struct sched_cell_configuration_request_message { /// Payload size is in bytes. unsigned sib1_payload_size; + /// Scheduling of SI messages. + optional si_scheduling; + /// List of PUCCH guardbands. std::vector pucch_guardbands; diff --git a/include/srsran/scheduler/scheduler_slot_handler.h b/include/srsran/scheduler/scheduler_slot_handler.h index 65f6e92537..940350442c 100644 --- a/include/srsran/scheduler/scheduler_slot_handler.h +++ b/include/srsran/scheduler/scheduler_slot_handler.h @@ -338,6 +338,7 @@ struct ssb_information { /// Stores the information associated with an SIB1 or other SI allocation. struct sib_information { enum si_indicator_type { sib1, other_si } si_indicator; + optional si_msg_index; unsigned nof_txs; pdsch_information pdsch_cfg; }; diff --git a/include/srsran/srslog/log_channel.h b/include/srsran/srslog/log_channel.h index 426393cc3e..9052c899fa 100644 --- a/include/srsran/srslog/log_channel.h +++ b/include/srsran/srslog/log_channel.h @@ -29,6 +29,14 @@ namespace srslog { +/// Type trait to indicate if a type that is going to be passed through a log channel is unsafe to be copied with the +/// default implementation and requires a user defined copy implementation. +template +struct copy_loggable_type { + static constexpr bool is_copyable = true; + // static void copy (fmt::dynamic_format_arg_store* store, a) +}; + /// Log channel configuration settings. struct log_channel_config { log_channel_config() = default; @@ -57,6 +65,19 @@ struct log_channel_config { /// NOTE: Thread safe class. class log_channel { + /// This push_back implementation wrapper takes into account types that cannot be safely copied and requires user + /// defined implementation. + template ::is_copyable, int> = 0> + void push_back(fmt::dynamic_format_arg_store* store, T&& arg) + { + store->push_back(std::forward(arg)); + } + template ::is_copyable, int> = 0> + void push_back(fmt::dynamic_format_arg_store* store, T&& arg) + { + copy_loggable_type::copy(store, std::forward(arg)); + } + public: log_channel(std::string id, sink& s, detail::log_backend& backend_) : log_channel(std::move(id), s, backend_, {}) {} @@ -107,7 +128,7 @@ class log_channel if (!store) { return; } - (void)std::initializer_list{(store->push_back(std::forward(args)), 0)...}; + (void)std::initializer_list{(push_back(store, std::forward(args)), 0)...}; // Send the log entry to the backend. log_formatter& formatter = log_sink.get_formatter(); @@ -138,7 +159,7 @@ class log_channel if (!store) { return; } - (void)std::initializer_list{(store->push_back(std::forward(args)), 0)...}; + (void)std::initializer_list{(push_back(store, std::forward(args)), 0)...}; // Calculate the length to capture in the buffer. if (hex_max_size >= 0) { @@ -175,7 +196,7 @@ class log_channel if (!store) { return; } - (void)std::initializer_list{(store->push_back(std::forward(args)), 0)...}; + (void)std::initializer_list{(push_back(store, std::forward(args)), 0)...}; // Calculate the length to capture in the buffer. if (hex_max_size >= 0 && hex_max_size < std::distance(it_begin, it_end)) { @@ -236,7 +257,7 @@ class log_channel if (!store) { return; } - (void)std::initializer_list{(store->push_back(std::forward(args)), 0)...}; + (void)std::initializer_list{(push_back(store, std::forward(args)), 0)...}; // Send the log entry to the backend. log_formatter& formatter = log_sink.get_formatter(); diff --git a/include/srsran/srsvec/bit.h b/include/srsran/srsvec/bit.h index 8c805d84b7..0af035969d 100644 --- a/include/srsran/srsvec/bit.h +++ b/include/srsran/srsvec/bit.h @@ -44,18 +44,18 @@ namespace srsvec { /// \return A view of the remaining (behind \c nof_bits) unpacked bits of \c value. span bit_unpack(span bits, unsigned value, unsigned nof_bits); -/// \brief Unpacks bytes into bits. -/// \param[out] unpacked View of the unpacked bits. -/// \param[in] packed View of the packed bits. -/// \remark The number of unpacked elements must be equal to eight times the number of packed elements. -void bit_unpack(span unpacked, span packed); - /// \brief Unpacks a bit buffer into bits. /// \param[out] unpacked View of the unpacked bits. /// \param[in] packed Bit buffer to unpack. /// \remark The number of unpacked elements must be equal to the packed number of bits. void bit_unpack(span unpacked, const bit_buffer& packed); +/// \brief Unpacks a bit buffer into bits. +/// \param[out] unpacked View of the unpacked bits. +/// \param[in] packed Bit buffer to unpack. +/// \param[in] offset Packed initial bit index. +void bit_unpack(span unpacked, const bit_buffer& packed, unsigned offset); + /// \brief Packs a number of bits into an integer value. /// \param[in,out] bits View of unpacked bits. /// \param[in] nof_bits Indicates the number of bits. @@ -75,18 +75,20 @@ unsigned bit_pack(span& bits, unsigned nof_bits); /// \remark The number of elements must not exceed 32 bits. unsigned bit_pack(span bits); -/// \brief Packs a number of bits into bytes. -/// \param[out] packed View of packed bits. -/// \param[in] unpacked View of unpacked bits. -/// \remark The number of unpacked elements must be equal to eight times the number packed elements. -void bit_pack(span packed, span unpacked); - /// \brief Packs a number of bits into a bit buffer. /// \param[out] packed Destination bit buffer. /// \param[in] unpacked View of unpacked bits. /// \remark The number of unpacked elements must be equal to the maximum number of bits supported by the bit buffer. void bit_pack(bit_buffer& packed, span unpacked); +/// \brief Packs a number of bits into a bit buffer. +/// \param[out] packed Destination bit buffer. +/// \param[in] offset Packed initial bit index. +/// \param[in] unpacked View of unpacked bits. +/// \remark The number of unpacked elements must be smaller than or equal to the maximum number of bits supported by the +/// bit buffer minus \c offset. +void bit_pack(bit_buffer& packed, unsigned offset, span unpacked); + /// \brief Copies \c output.size() bits from \c input, starting at \c startpos, into \c output. /// \param[out] output Destination of the copy. /// \param[in] input Data source to copy. diff --git a/include/srsran/support/async/async_task_loop.h b/include/srsran/support/async/async_task_loop.h deleted file mode 100644 index d371689d32..0000000000 --- a/include/srsran/support/async/async_task_loop.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * - * Copyright 2021-2023 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#pragma once - -#include "async_queue.h" -#include "async_task.h" -#include "eager_async_task.h" -#include "srsran/adt/unique_function.h" - -namespace srsran { -/// Asynchronous task that sequentially runs other enqueued asynchronous tasks -class async_task_sequencer -{ -public: - async_task_sequencer(size_t queue_size) : queue(queue_size) { run(); } - async_task_sequencer(const async_task_sequencer&) = delete; - async_task_sequencer& operator=(const async_task_sequencer&) = delete; - - template - bool schedule(async_task&& t) - { - return queue.try_push(std::move(t)); - } - - template - bool schedule(Args&&... args) - { - return queue.try_push(launch_async(std::forward(args)...)); - } - - template - bool schedule(AsyncFunc&& async_func) - { - return queue.try_push(launch_async(std::forward(async_func))); - } - - void clear_pending_tasks() { queue.clear(); } - - eager_async_task request_stop() - { - // Enqueue task in case main loop is waiting for new procedure - running = false; - queue.try_push(launch_async([](coro_context>& ctx) { - CORO_BEGIN(ctx); - CORO_RETURN(); - })); - return std::move(loop_task); - } - - size_t nof_pending_tasks() const { return queue.size(); } - - bool empty() const { return queue.size() == 0; } - - bool is_stopped() const { return loop_task.empty(); } - -private: - void run() - { - loop_task = launch_async([this](coro_context>& ctx) { - CORO_BEGIN(ctx); - - // runs until requested to stop. - while (running) { - // Wait for new procedure to be enqueued. - CORO_AWAIT_VALUE(next_task, queue); - - // Await for popped task to complete - CORO_AWAIT(next_task); - } - - CORO_RETURN(); - }); - } - - bool running = true; - async_queue> queue; - eager_async_task loop_task; - async_task next_task; -}; - -} // namespace srsran diff --git a/include/srsran/support/async/event_sender_receiver.h b/include/srsran/support/async/event_sender_receiver.h new file mode 100644 index 0000000000..6c07b603a8 --- /dev/null +++ b/include/srsran/support/async/event_sender_receiver.h @@ -0,0 +1,245 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/optional.h" +#include "srsran/support/async/coroutine.h" + +namespace srsran { + +template +class event_receiver; + +template +class event_sender +{ + event_sender(event_receiver& receiver_) : receiver(&receiver_) {} + +public: + event_sender(const event_sender&) = delete; + event_sender(event_sender&& other) noexcept : receiver(std::exchange(other.receiver, nullptr)) {} + event_sender& operator=(const event_sender&) = delete; + event_sender& operator=(event_sender&& other) noexcept + { + receiver = std::exchange(other.receiver, nullptr); + return *this; + } + ~event_sender() + { + if (receiver != nullptr) { + receiver->cancel(); + } + } + + template + void set(Result&& data) + { + static_assert(std::is_convertible::value, "Unable to convert Result to Data type"); + srsran_assert(receiver != nullptr, "Sender not connected to receiver"); + receiver->set_result(std::forward(data)); + receiver = nullptr; + } + +private: + friend class event_receiver; + + event_receiver* receiver = nullptr; +}; + +template <> +class event_sender +{ + event_sender(event_receiver& receiver_) : receiver(&receiver_) {} + +public: + event_sender(const event_sender&) = delete; + event_sender(event_sender&& other) noexcept : receiver(std::exchange(other.receiver, nullptr)) {} + event_sender& operator=(const event_sender&) = delete; + event_sender& operator=(event_sender&& other) noexcept + { + receiver = std::exchange(other.receiver, nullptr); + return *this; + } + ~event_sender(); + + void set(); + +private: + friend class event_receiver; + + event_receiver* receiver = nullptr; +}; + +template +class event_receiver +{ + enum class state_t : int8_t { uninit, unset, cancelled, set }; + +public: + event_receiver() = default; + event_receiver(const event_receiver&) = delete; + event_receiver(event_receiver&&) = delete; + event_receiver& operator=(const event_receiver&) = delete; + event_receiver& operator=(event_receiver&&) = delete; + ~event_receiver() + { + srsran_assert(state != state_t::unset, "Event receiver destroyed without being set or cancelled"); + } + + event_sender get_sender() + { + srsran_assert(state == state_t::uninit, "Only one sender can be connected to an event receiver"); + state = state_t::unset; + return event_sender(*this); + } + + bool completed() const { return state == state_t::set or state == state_t::cancelled; } + bool successful() const { return state == state_t::set; } + bool aborted() const { return state == state_t::cancelled; } + + const Data& result() const + { + srsran_assert(successful(), "The event result has not be set"); + return data.value(); + } + + // Awaiter/Awaitable interface. + event_receiver& get_awaiter() { return *this; } + bool await_ready() const { return completed(); } + void await_suspend(coro_handle<> c) + { + // Store suspending coroutine. + suspended_handle = c; + } + const optional& await_resume() { return data; } + +private: + friend class event_sender; + + void set_result(const Data& result) + { + srsran_assert(state == state_t::unset, "The event cannot be set multiple times"); + state = state_t::set; + this->data = result; + + // Awake the awaiting coroutine. + if (not suspended_handle.empty()) { + suspended_handle.resume(); + } + } + void cancel() + { + srsran_assert(state == state_t::unset, "The event cannot be set multiple times"); + state = state_t::cancelled; + + // Awake the awaiting coroutine. + if (not suspended_handle.empty()) { + suspended_handle.resume(); + } + } + + state_t state = state_t::uninit; + coro_handle<> suspended_handle; + optional data; +}; + +template <> +class event_receiver +{ + enum class state_t : int8_t { uninit, unset, cancelled, set }; + +public: + event_receiver() = default; + event_receiver(const event_receiver&) = delete; + event_receiver(event_receiver&&) = delete; + event_receiver& operator=(const event_receiver&) = delete; + event_receiver& operator=(event_receiver&&) = delete; + ~event_receiver() + { + srsran_assert(state != state_t::unset, "Event receiver destroyed without being set or cancelled"); + } + + event_sender get_sender() + { + srsran_assert(state == state_t::uninit, "Only one sender can be connected to an event receiver"); + state = state_t::unset; + return event_sender(*this); + } + + bool completed() const { return state == state_t::set or state == state_t::cancelled; } + bool successful() const { return state == state_t::set; } + bool aborted() const { return state == state_t::cancelled; } + + // Awaiter/Awaitable interface. + event_receiver& get_awaiter() { return *this; } + bool await_ready() const { return completed(); } + void await_suspend(coro_handle<> c) + { + // Store suspending coroutine. + suspended_handle = c; + } + bool await_resume() { return state == state_t::set; } + +private: + friend class event_sender; + + void set_result() + { + srsran_assert(state == state_t::unset, "The event cannot be set multiple times"); + state = state_t::set; + + // Awake the awaiting coroutine. + if (not suspended_handle.empty()) { + suspended_handle.resume(); + } + } + void cancel() + { + srsran_assert(state == state_t::unset, "The event cannot be set multiple times"); + state = state_t::cancelled; + + // Awake the awaiting coroutine. + if (not suspended_handle.empty()) { + suspended_handle.resume(); + } + } + + state_t state = state_t::uninit; + coro_handle<> suspended_handle; +}; + +inline event_sender::~event_sender() +{ + if (receiver != nullptr) { + receiver->cancel(); + } +} + +inline void event_sender::set() +{ + srsran_assert(receiver != nullptr, "Sender not connected to receiver"); + receiver->set_result(); + receiver = nullptr; +} + +} // namespace srsran \ No newline at end of file diff --git a/include/srsran/support/async/fifo_async_task_scheduler.h b/include/srsran/support/async/fifo_async_task_scheduler.h new file mode 100644 index 0000000000..d5b4939927 --- /dev/null +++ b/include/srsran/support/async/fifo_async_task_scheduler.h @@ -0,0 +1,199 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "async_queue.h" +#include "async_task.h" +#include "eager_async_task.h" +#include "srsran/adt/unique_function.h" +#include "srsran/support/async/event_sender_receiver.h" + +namespace srsran { + +/// Asynchronous task that sequentially runs other enqueued asynchronous tasks +class fifo_async_task_scheduler +{ +public: + fifo_async_task_scheduler(size_t queue_size) : queue(queue_size) { run(); } + fifo_async_task_scheduler(const fifo_async_task_scheduler&) = delete; + fifo_async_task_scheduler& operator=(const fifo_async_task_scheduler&) = delete; + + template + bool schedule(async_task&& t) + { + return queue.try_push(std::move(t)); + } + + template + bool schedule(Args&&... args) + { + return queue.try_push(launch_async(std::forward(args)...)); + } + + template + bool schedule(AsyncFunc&& async_func) + { + return queue.try_push(launch_async(std::forward(async_func))); + } + + void clear_pending_tasks() { queue.clear(); } + + eager_async_task request_stop() + { + // Enqueue task in case main loop is waiting for new procedure + running = false; + queue.try_push(launch_async([](coro_context>& ctx) { + CORO_BEGIN(ctx); + CORO_RETURN(); + })); + return std::move(loop_task); + } + + size_t nof_pending_tasks() const { return queue.size(); } + + bool empty() const { return queue.size() == 0; } + + bool is_stopped() const { return loop_task.empty(); } + +private: + void run() + { + loop_task = launch_async([this](coro_context>& ctx) { + CORO_BEGIN(ctx); + + // runs until requested to stop. + while (running) { + // Wait for new procedure to be enqueued. + CORO_AWAIT_VALUE(next_task, queue); + + // Await for popped task to complete + CORO_AWAIT(next_task); + } + + CORO_RETURN(); + }); + } + + bool running = true; + async_queue> queue; + eager_async_task loop_task; + async_task next_task; +}; + +/// \brief Launches an asynchronous task on the given task sequencer and returns an async task that is only complete +/// when the former is complete. +/// +/// This function is useful to synchronize two procedures running in separate task schedulers. +/// \tparam Callback +/// \tparam ReturnType +/// \param task_sched +/// \param task_to_run +/// \return +template ::operator())>, + std::enable_if_t::value, int> = 0> +async_task when_completed_on_task_sched(fifo_async_task_scheduler& task_sched, Callback&& task_to_run) +{ + struct task_offloader { + task_offloader(fifo_async_task_scheduler& task_sched_, Callback&& callback_) : + task_sched(task_sched_), callback(std::forward(callback_)) + { + } + + void operator()(coro_context>& ctx) + { + CORO_BEGIN(ctx); + + task_sched.schedule(dispatched_task()); + + CORO_AWAIT_VALUE(const bool result, rx); + + CORO_RETURN(result); + } + + private: + async_task dispatched_task() + { + return launch_async([this, tx = rx.get_sender()](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + + callback(); + + tx.set(); + + CORO_RETURN(); + }); + } + + fifo_async_task_scheduler& task_sched; + Callback callback; + event_receiver rx; + }; + + return launch_async(task_sched, std::forward(task_to_run)); +} + +template ::operator())>, + std::enable_if_t::value, int> = 0> +async_task> when_completed_on_task_sched(fifo_async_task_scheduler& task_sched, + Callback&& task_to_run) +{ + struct task_offloader { + task_offloader(fifo_async_task_scheduler& task_sched_, Callback&& callback_) : + task_sched(task_sched_), callback(std::forward(callback_)) + { + } + + void operator()(coro_context>>& ctx) + { + CORO_BEGIN(ctx); + + task_sched.schedule(dispatched_task()); + + CORO_AWAIT_VALUE(optional result, rx); + + CORO_RETURN(result); + } + + private: + async_task dispatched_task() + { + return launch_async([this, tx = rx.get_sender()](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + + tx.set(callback()); + + CORO_RETURN(); + }); + } + + fifo_async_task_scheduler& task_sched; + Callback callback; + event_receiver rx; + }; + + return launch_async(task_sched, std::forward(task_to_run)); +} + +} // namespace srsran diff --git a/include/srsran/support/benchmark_utils.h b/include/srsran/support/benchmark_utils.h index 2a3d79f9dc..08ada0f121 100644 --- a/include/srsran/support/benchmark_utils.h +++ b/include/srsran/support/benchmark_utils.h @@ -162,6 +162,10 @@ class benchmarker return static_cast(throughput) * 0.1; } + struct do_nothing_functor { + void operator()() {} + }; + public: /// \brief Creates a bench marker. /// @@ -243,12 +247,18 @@ class benchmarker } /// \brief Performs a new performance measurement. - /// \tparam Func Lambda type to perform the benchmark. - /// \param[in] description Measurement description for later reporting. - /// \param[in] size Number of elements processed in the measurement. - /// \param[in] function Lambda function to call repeatedly. - template - void new_measure(const std::string& description, uint64_t size, Func&& function) + /// \tparam Func Lambda type to perform the benchmark. + /// \tparam PostFunc Lambda type to call after each Func call. + /// \param[in] description Measurement description for later reporting. + /// \param[in] size Number of elements processed in the measurement. + /// \param[in] function Lambda function to call repeatedly. + /// \param[in] post_func Lambda function to call repeatedly, after \c function, but without being accounted for + /// in the benchmark results. + template + void new_measure(const std::string& description, + uint64_t size, + Func&& function, + PostFunc&& post_func = do_nothing_functor{}) { benchmark_result result; result.description = description; @@ -260,6 +270,7 @@ class benchmarker function(); auto end = std::chrono::high_resolution_clock::now(); result.measurements.push_back(std::chrono::duration_cast(end - start).count()); + post_func(); } std::sort(result.measurements.begin(), result.measurements.end()); diff --git a/include/srsran/support/bit_encoding.h b/include/srsran/support/bit_encoding.h index 88b207bed1..a1701b3cd7 100644 --- a/include/srsran/support/bit_encoding.h +++ b/include/srsran/support/bit_encoding.h @@ -54,7 +54,8 @@ class bit_encoder /// - pack(0b01100, 5): [...][11101101][100_____] /// \param val bitmap to be packed. /// \param n_bits number of bits to pack. - void pack(uint64_t val, uint32_t n_bits); + /// \return success or failure. + bool pack(uint64_t val, uint32_t n_bits); /// Append range of bytes into byte_buffer held by bit_encoder. /// \param bytes span of bytes. diff --git a/include/srsran/support/executors/priority_multiqueue_task_worker.h b/include/srsran/support/executors/priority_multiqueue_task_worker.h deleted file mode 100644 index 4e8b8c5934..0000000000 --- a/include/srsran/support/executors/priority_multiqueue_task_worker.h +++ /dev/null @@ -1,280 +0,0 @@ -/* - * - * Copyright 2021-2023 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#pragma once - -#include "srsran/adt/concurrent_queue.h" -#include "srsran/support/executors/task_executor.h" -#include "srsran/support/unique_thread.h" - -namespace srsran { - -/// \brief Task queue priority used to map to specific queue of the \c priority_multiqueue_task_worker. The higher the -/// priority, the lower its integer value representation. -enum class task_queue_priority : size_t { max = 0, min = std::numeric_limits::max() }; - -/// Reduce priority by \c dec amount. -constexpr task_queue_priority operator-(task_queue_priority lhs, size_t dec) -{ - return (task_queue_priority)((size_t)lhs + dec); -} - -namespace detail { - -template -constexpr auto as_tuple(const std::array& arr, std::index_sequence /*unused*/) -{ - return std::make_tuple(arr[Is]...); -} - -template -constexpr auto as_tuple(const std::array& arr) -{ - return as_tuple(arr, std::make_index_sequence{}); -} - -template -constexpr void for_each_impl(T&& t, const F& f, std::index_sequence) -{ - (void)std::initializer_list{(f(std::get(t)), 0)...}; -} - -template -constexpr void for_each(T&& t, const F& f) -{ - for_each_impl(t, f, std::make_index_sequence>::value>{}); -} - -template -constexpr bool any_of_impl(Tuple&&, Pred&&, std::index_sequence<>) -{ - return false; -} - -template -constexpr bool any_of_impl(Tuple&& t, Pred&& pred, std::index_sequence) -{ - return pred(std::get(t)) || any_of_impl(t, std::forward(pred), std::index_sequence{}); -} - -template -constexpr bool any_of(std::tuple& t, Pred&& pred) -{ - return any_of_impl(t, std::forward(pred), std::index_sequence_for{}); -} - -} // namespace detail - -/// \brief Task worker that can handle tasks with different priorities. Each priority level gets associated a separate -/// task queue with queueing policy defined by \c concurrent_queue_policy. The task worker will pop tasks starting from -/// the highest priority queue and will continue to the next priority level if the current queue is empty. If there are -/// no tasks in any of the queues, the task worker will wait for \c wait_for_task_sleep before checking for new tasks. -/// -/// \tparam QueuePolicies Queue policies for each priority level. The number of policies must match the number of -/// supported priority levels. -template -class priority_multiqueue_task_worker -{ -public: - /// \brief Construct a new priority multiqueue task worker object. - /// \param thread_name The name of the thread used by the task worker. - /// \param task_queue_sizes The sizes of the task queues for each priority level. - /// \param wait_for_task_sleep_ The amount of time to suspend the thread, when no tasks are pending. - /// \param prio task worker OS thread priority level. - /// \param mask thread OS affinity bitmask. - priority_multiqueue_task_worker(std::string thread_name, - const std::array& task_queue_sizes, - std::chrono::microseconds wait_for_task_sleep_, - os_thread_realtime_priority prio = os_thread_realtime_priority::no_realtime(), - const os_sched_affinity_bitmask& mask = {}) : - wait_for_task_sleep(wait_for_task_sleep_), - task_queues(detail::as_tuple(task_queue_sizes)), - t_handle(thread_name, prio, mask, [this]() { run_pop_task_loop(); }) - { - } - ~priority_multiqueue_task_worker() { stop(); } - - /// Stop task worker, if running. - void stop() - { - if (t_handle.running()) { - running = false; - detail::for_each(task_queues, [](auto& queue) { queue.request_stop(); }); - t_handle.join(); - } - } - - /// \brief Push task to task queue with priority level \c Priority (lower integer represents a higher priority). - template - SRSRAN_NODISCARD bool push_task(unique_task task) - { - return std::get(task_queues).try_push(std::move(task)); - } - - /// \brief Get specified priority task queue capacity. - template - SRSRAN_NODISCARD size_t queue_capacity() const - { - return std::get(task_queues).capacity(); - } - - /// Get worker thread id. - std::thread::id get_id() const { return t_handle.get_id(); } - - /// Get worker thread name. - const char* worker_name() const { return t_handle.get_name(); } - - /// Number of priority levels supported by this worker. - static constexpr size_t nof_priority_levels() { return sizeof...(QueuePolicies); } - -private: - void run_pop_task_loop() - { - while (running.load(std::memory_order_relaxed)) { - if (not detail::any_of(task_queues, - [](auto& queue) mutable { return queue.try_pop([](const unique_task& t) { t(); }); })) { - // If no task was run, sleep for defined time interval. - std::this_thread::sleep_for(wait_for_task_sleep); - } - } - srslog::fetch_basic_logger("ALL").info("Task worker \"{}\" finished.", this_thread_name()); - } - - static constexpr size_t task_queue_priority_to_index(task_queue_priority prio) - { - return static_cast(prio) < nof_priority_levels() ? static_cast(prio) : nof_priority_levels() - 1; - } - - // Time that thread spends sleeping when there are no pending tasks. - const std::chrono::microseconds wait_for_task_sleep; - - // Task queues with different priorities. The first queue is the highest priority queue. - std::tuple...> task_queues; - - // Status of the task worker. - std::atomic running{true}; - - // Worker thread - unique_thread t_handle; -}; - -/// \brief Task executor with \c Priority (lower is higher) for \c priority_multiqueue_task_worker. -template -class priority_task_worker_executor : public task_executor -{ -public: - constexpr static task_queue_priority priority_level = Priority; - - priority_task_worker_executor(priority_multiqueue_task_worker& worker_, - bool report_on_push_failure = true) : - worker(&worker_), report_on_failure(report_on_push_failure) - { - } - - bool execute(unique_task task) override - { - if (worker->get_id() == std::this_thread::get_id()) { - // Same thread. Run task right away. - task(); - return true; - } - return defer(std::move(task)); - } - - bool defer(unique_task task) override - { - if (not worker->template push_task(std::move(task))) { - if (report_on_failure) { - srslog::fetch_basic_logger("ALL").warning( - "Cannot push more tasks into the {} worker queue with priority={}. Maximum size is {}", - worker->worker_name(), - Priority, - worker->template queue_capacity()); - } - return false; - } - return true; - } - -private: - priority_multiqueue_task_worker* worker = nullptr; - bool report_on_failure = true; -}; - -/// \brief Create task executor with \c Priority for \c priority_multiqueue_task_worker. -template -priority_task_worker_executor -make_priority_task_worker_executor(priority_multiqueue_task_worker& worker) -{ - return priority_task_worker_executor(worker); -} - -/// \brief Create general task executor pointer with \c Priority for \c priority_multiqueue_task_worker. -template -std::unique_ptr -make_priority_task_executor_ptr(priority_multiqueue_task_worker& worker) -{ - return std::make_unique>(worker); -} - -namespace detail { - -template -void visit_executor(priority_multiqueue_task_worker& w, - task_queue_priority priority, - const Func& func, - std::index_sequence /*unused*/) -{ - (void)std::initializer_list{[priority, &w, &func]() { - if (priority == static_cast(Is)) { - func(make_priority_task_worker_executor(Is)>(w)); - } - return 0; - }()...}; -} - -} // namespace detail - -/// \brief Create general task executor pointer with \c Priority for \c priority_multiqueue_task_worker. -template -void visit_executor(priority_multiqueue_task_worker& worker, - task_queue_priority priority, - const Func& func) -{ - detail::visit_executor(worker, priority, func, std::make_index_sequence{}); -} - -/// \brief Create general task executor pointer with \c Priority for \c priority_multiqueue_task_worker. -template -std::unique_ptr -make_priority_task_executor_ptr(priority_multiqueue_task_worker& worker, task_queue_priority priority) -{ - std::unique_ptr exec; - detail::visit_executor( - worker, - priority, - [&exec](const auto& e) { exec = std::make_unique(e); }, - std::make_index_sequence{}); - return exec; -} - -} // namespace srsran diff --git a/include/srsran/support/executors/priority_task_worker.h b/include/srsran/support/executors/priority_task_worker.h new file mode 100644 index 0000000000..6c6222a238 --- /dev/null +++ b/include/srsran/support/executors/priority_task_worker.h @@ -0,0 +1,232 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/concurrent_queue.h" +#include "srsran/adt/detail/tuple_utils.h" +#include "srsran/support/executors/task_executor.h" +#include "srsran/support/unique_thread.h" + +namespace srsran { + +/// \brief Priority level of an enqueued task in \c priority_multiqueue_task_worker. +using task_priority = enqueue_priority; + +/// \brief Task worker that can handle tasks with different levels of priority. +/// +/// Each priority level gets associated a separate task queue with queueing policy defined by +/// \c concurrent_queue_policy. The task worker will pop tasks starting from the highest priority queue and will +/// continue to the next priority level if the current queue is empty. If there are no tasks in any of the queues, the +/// task worker will wait for \c wait_interval before checking for new tasks. +/// +/// \tparam QueuePolicies Queue policies for each priority level. The number of policies must match the number of +/// supported priority levels. +template +class priority_task_worker +{ +public: + /// \brief Construct a new priority multiqueue task worker object. + /// \param thread_name The name of the thread used by the task worker. + /// \param task_queue_sizes The sizes of the task queues for each priority level. + /// \param wait_interval The amount of time to suspend the thread, when no tasks are pending. + /// \param prio task worker OS thread priority level. + /// \param mask thread OS affinity bitmask. + priority_task_worker(std::string thread_name, + const std::array& task_queue_sizes, + std::chrono::microseconds wait_interval, + os_thread_realtime_priority prio = os_thread_realtime_priority::no_realtime(), + const os_sched_affinity_bitmask& mask = {}) : + task_queue(task_queue_sizes, wait_interval), t_handle(thread_name, prio, mask, [this]() { run_pop_task_loop(); }) + { + } + ~priority_task_worker() { stop(); } + + /// Stop task worker, if running. + void stop() + { + if (t_handle.running()) { + task_queue.request_stop(); + t_handle.join(); + } + } + + /// \brief Push task to task queue with priority level \c Priority (lower integer represents a higher priority). + template + SRSRAN_NODISCARD bool push_task(unique_task task) + { + return task_queue.template try_push(std::move(task)); + } + + /// \brief Get specified priority task queue capacity. + template + SRSRAN_NODISCARD size_t queue_capacity() const + { + return task_queue.template capacity(); + } + + /// Get worker thread id. + std::thread::id get_id() const { return t_handle.get_id(); } + + /// Get worker thread name. + const char* worker_name() const { return t_handle.get_name(); } + + /// Number of priority levels supported by this worker. + static constexpr size_t nof_priority_levels() { return sizeof...(QueuePolicies); } + +private: + void run_pop_task_loop() + { + while (task_queue.call_on_pop_blocking([](unique_task& task) { task(); })) { + } + srslog::fetch_basic_logger("ALL").info("Task worker \"{}\" finished.", this_thread_name()); + } + + // Task queues with different priorities. The first queue is the highest priority queue. + concurrent_priority_queue task_queue; + + // Worker thread + unique_thread t_handle; +}; + +/// \brief Task executor with \c Priority (lower is higher) for \c priority_multiqueue_task_worker. +template +class priority_task_worker_executor : public task_executor +{ +public: + constexpr static task_priority priority_level = Priority; + + priority_task_worker_executor(priority_task_worker& worker_, bool report_on_push_failure = true) : + worker(&worker_), report_on_failure(report_on_push_failure) + { + } + + SRSRAN_NODISCARD bool execute(unique_task task) override + { + if (Priority == task_priority::max and worker->get_id() == std::this_thread::get_id()) { + // If same thread and highest priority task, run task right away. + task(); + return true; + } + return defer(std::move(task)); + } + + SRSRAN_NODISCARD bool defer(unique_task task) override + { + if (not worker->template push_task(std::move(task))) { + if (report_on_failure) { + srslog::fetch_basic_logger("ALL").warning( + "Cannot push more tasks into the {} worker queue with priority={}. Maximum size is {}", + worker->worker_name(), + Priority, + worker->template queue_capacity()); + } + return false; + } + return true; + } + +private: + priority_task_worker* worker = nullptr; + bool report_on_failure = true; +}; + +/// \brief Create task executor with \c Priority for \c priority_multiqueue_task_worker. +template +priority_task_worker_executor +make_priority_task_worker_executor(priority_task_worker& worker) +{ + return priority_task_worker_executor(worker); +} + +/// \brief Create general task executor pointer with \c Priority for \c priority_multiqueue_task_worker. +template +std::unique_ptr make_priority_task_executor_ptr(priority_task_worker& worker) +{ + return std::make_unique>(worker); +} + +namespace detail { + +template +void visit_executor(priority_task_worker& w, + task_priority priority, + const Func& func, + std::index_sequence /*unused*/) +{ + const size_t idx = detail::enqueue_priority_to_queue_index(priority, sizeof...(QueuePolicies)); + (void)std::initializer_list{[idx, &w, &func]() { + if (idx == Is) { + func( + make_priority_task_worker_executor(w)); + } + return 0; + }()...}; +} + +} // namespace detail + +/// \brief Create general task executor pointer with \c Priority for \c priority_multiqueue_task_worker. +template +void visit_executor(priority_task_worker& worker, task_priority priority, const Func& func) +{ + detail::visit_executor(worker, priority, func, std::make_index_sequence{}); +} + +/// \brief Create general task executor pointer with \c Priority for \c priority_multiqueue_task_worker. +template +std::unique_ptr make_priority_task_executor_ptr(priority_task_worker& worker, + task_priority priority) +{ + std::unique_ptr exec; + detail::visit_executor( + worker, + priority, + [&exec](const auto& e) { exec = std::make_unique(e); }, + std::make_index_sequence{}); + return exec; +} + +} // namespace srsran + +namespace fmt { + +template <> +struct formatter { + template + auto parse(ParseContext& ctx) + { + return ctx.begin(); + } + + template + auto format(const srsran::task_priority& prio, FormatContext& ctx) + { + fmt::format_to(ctx.out(), "max"); + if (prio != srsran::task_priority::max) { + fmt::format_to(ctx.out(), "-{}", static_cast(srsran::task_priority::max) - static_cast(prio)); + } + return ctx.out(); + } +}; + +} // namespace fmt diff --git a/include/srsran/support/executors/sync_task_executor.h b/include/srsran/support/executors/sync_task_executor.h index ef26a6e7fa..316b1ef7f2 100644 --- a/include/srsran/support/executors/sync_task_executor.h +++ b/include/srsran/support/executors/sync_task_executor.h @@ -51,7 +51,12 @@ class sync_task_executor final : public task_executor { done = false; std::unique_ptr unique_setter(&done, setter_deleter{this}); - bool ret = get(exec).execute([task = std::move(task), token = std::move(unique_setter)]() { task(); }); + bool ret = get(exec).execute([task = std::move(task), token = std::move(unique_setter)]() mutable { + task(); + // We manually reset the token here, as the unique_task being popped may be deleted in an undetermined time (it + // is up to the caller). + token.reset(); + }); // wait for condition variable to be set. std::unique_lock lock(mutex); diff --git a/include/srsran/support/executors/task_execution_manager.h b/include/srsran/support/executors/task_execution_manager.h index b6fd1df53a..6dac709c5d 100644 --- a/include/srsran/support/executors/task_execution_manager.h +++ b/include/srsran/support/executors/task_execution_manager.h @@ -94,6 +94,9 @@ struct worker_pool { task_queue queue; /// Executors associated with this execution context. std::vector executors; + /// \brief Wait time in microseconds, when task queue has no pending tasks. This value should be set when the queue + /// policy is lockfree MPMC. + optional sleep_time; /// OS priority of the worker thread. os_thread_realtime_priority prio = os_thread_realtime_priority::no_realtime(); /// Array of CPU bitmasks to assign to each worker in the pool. @@ -102,12 +105,7 @@ struct worker_pool { file_event_tracer* tracer = nullptr; }; -/// Priorities to assign to different types of tasks dispatched to a worker. -enum class task_priority : int { min = std::numeric_limits::min(), max = 0 }; -inline task_priority operator-(task_priority prio, unsigned diff) -{ - return static_cast((int)prio - (int)diff); -} +using task_priority = enqueue_priority; /// Arguments for the creation of a priority multiqueue worker. struct priority_multiqueue_worker { @@ -186,7 +184,7 @@ class task_execution_manager SRSRAN_NODISCARD bool add_execution_context(std::unique_ptr ctxt); /// Returns a table of all executors stored in the repository. - SRSRAN_FORCE_INLINE SRSRAN_NODISCARD const executor_table& executors() const { return executor_list; } + SRSRAN_NODISCARD const executor_table& executors() const { return executor_list; } private: srslog::basic_logger& logger; diff --git a/include/srsran/support/executors/task_worker_pool.h b/include/srsran/support/executors/task_worker_pool.h index 3bb6f5c819..ca1a23d903 100644 --- a/include/srsran/support/executors/task_worker_pool.h +++ b/include/srsran/support/executors/task_worker_pool.h @@ -22,7 +22,7 @@ #pragma once -#include "srsran/adt/blocking_queue.h" +#include "srsran/adt/concurrent_queue.h" #include "srsran/support/executors/task_executor.h" #include "srsran/support/unique_thread.h" @@ -30,8 +30,16 @@ namespace srsran { /// \brief Simple pool of task workers/threads. The workers share the same queue of task and do not perform /// work-stealing. +template class task_worker_pool { + // Queue type. + using queue_type = + concurrent_queue; + public: /// \brief Creates a task worker pool. /// \param nof_workers Number of workers of the worker pool. @@ -39,11 +47,33 @@ class task_worker_pool /// \param pool_name String with the name for the worker pool. Individual workers of the pool will be assigned the /// name "#". E.g. for pool_name="Pool", the second worker will be called "Pool#1". /// \param prio Workers realtime thread priority. + template = 0> task_worker_pool(unsigned nof_workers, unsigned queue_size, - const std::string& pool_name, + std::string worker_pool_name, + std::chrono::microseconds wait_sleep_time, os_thread_realtime_priority prio = os_thread_realtime_priority::no_realtime(), - span cpu_masks = {}); + span cpu_masks = {}) : + pool_name(std::move(worker_pool_name)), + logger(srslog::fetch_basic_logger("ALL")), + workers(nof_workers), + pending_tasks(queue_size, wait_sleep_time) + { + start_impl(prio, cpu_masks); + } + template = 0> + task_worker_pool(unsigned nof_workers, + unsigned queue_size, + std::string worker_pool_name, + os_thread_realtime_priority prio = os_thread_realtime_priority::no_realtime(), + span cpu_masks = {}) : + pool_name(std::move(worker_pool_name)), + logger(srslog::fetch_basic_logger("ALL")), + workers(nof_workers), + pending_tasks(queue_size) + { + start_impl(prio, cpu_masks); + } task_worker_pool(const task_worker_pool&) = delete; task_worker_pool(task_worker_pool&&) = delete; task_worker_pool& operator=(const task_worker_pool&) = delete; @@ -63,13 +93,13 @@ class task_worker_pool /// return false. /// \param task Task to be run in the thread pool. /// \return True if task was successfully enqueued to be processed. False, if task queue is full. - bool push_task(unique_task&& task) + SRSRAN_NODISCARD bool push_task(unique_task task) { - auto ret = pending_tasks.try_push(std::move(task)); - if (ret.is_error()) { - logger.error("Cannot push anymore tasks into the {} worker pool queue. maximum size is {}", + bool success = pending_tasks.try_push(std::move(task)); + if (not success) { + logger.error("Cannot push anymore tasks into the {} worker pool queue. Maximum size is {}", pool_name, - uint32_t(pending_tasks.max_size())); + pending_tasks.capacity()); return false; } return true; @@ -77,10 +107,10 @@ class task_worker_pool /// \brief Push a new task to be processed by the worker pool. If the task queue is full, blocks. /// \param task Task to be run in the thread pool. - void push_task_blocking(unique_task&& task) + void push_task_blocking(unique_task task) { - auto ret = pending_tasks.push_blocking(std::move(task)); - if (ret.is_error()) { + bool success = pending_tasks.push_blocking(std::move(task)); + if (not success) { logger.debug("Cannot push anymore tasks into the {} worker queue because it was closed", pool_name); return; } @@ -93,11 +123,27 @@ class task_worker_pool /// Name given to the pool. const std::string& name() const { return pool_name; } + /// Determines whether the caller is inside the pool. + bool is_in_thread_pool() const + { + thread_local const bool inside_pool_flag = [this, id = std::this_thread::get_id()]() { + for (const worker& w : workers) { + if (w.t_handle.get_id() == id) { + return true; + } + } + return false; + }(); + return inside_pool_flag; + } + private: struct worker { unique_thread t_handle; }; + void start_impl(os_thread_realtime_priority prio_, span cpu_masks); + std::string pool_name; srslog::basic_logger& logger; @@ -105,22 +151,26 @@ class task_worker_pool std::vector workers; // Queue of tasks. - srsran::blocking_queue pending_tasks; + queue_type pending_tasks; }; +extern template class task_worker_pool; +extern template class task_worker_pool; + /// \brief Task executor that pushes tasks to worker pool. +template class task_worker_pool_executor final : public task_executor { public: task_worker_pool_executor() = default; - task_worker_pool_executor(task_worker_pool& worker_pool_) : worker_pool(&worker_pool_) {} + task_worker_pool_executor(task_worker_pool& worker_pool_) : worker_pool(&worker_pool_) {} bool execute(unique_task task) override { return worker_pool->push_task(std::move(task)); } bool defer(unique_task task) override { return worker_pool->push_task(std::move(task)); } private: - task_worker_pool* worker_pool = nullptr; + task_worker_pool* worker_pool = nullptr; }; -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/include/srsran/support/io/io_broker.h b/include/srsran/support/io/io_broker.h index 429e11deb9..4a00541eee 100644 --- a/include/srsran/support/io/io_broker.h +++ b/include/srsran/support/io/io_broker.h @@ -26,6 +26,14 @@ namespace srsran { +struct io_broker_config { + std::string thread_name = "io_broker_epoll"; + os_thread_realtime_priority thread_prio = os_thread_realtime_priority::no_realtime(); + os_sched_affinity_bitmask cpu_mask = {}; + /// Constructor receives CPU affinity mask. + io_broker_config(os_sched_affinity_bitmask mask_ = {}) : cpu_mask(mask_) {} +}; + /// \brief Describes the base interface for an (async) IO broker. /// The IO broker is responsible for handling all IO events, including /// sockets, signals, and system timers. diff --git a/include/srsran/support/io/io_broker_factory.h b/include/srsran/support/io/io_broker_factory.h index b89c912280..457df1889a 100644 --- a/include/srsran/support/io/io_broker_factory.h +++ b/include/srsran/support/io/io_broker_factory.h @@ -30,6 +30,6 @@ namespace srsran { enum class io_broker_type { epoll, io_uring }; /// Creates an instance of an IO broker -std::unique_ptr create_io_broker(io_broker_type type); +std::unique_ptr create_io_broker(io_broker_type type, io_broker_config config = {}); } // namespace srsran diff --git a/include/srsran/support/memory_pool/concurrent_thread_local_object_pool.h b/include/srsran/support/memory_pool/concurrent_thread_local_object_pool.h new file mode 100644 index 0000000000..21dd0fe6a1 --- /dev/null +++ b/include/srsran/support/memory_pool/concurrent_thread_local_object_pool.h @@ -0,0 +1,149 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/concurrent_queue.h" +#include + +namespace srsran { + +/// \brief Concurrent pool of instances that are accessed depending on the thread. +/// +/// The pool contains a vector of instances that are assigned to threads the first time they call get(). The instances +/// are returned to the pool when the thread is terminated. +/// +/// The main thread is a special case as it does not get deleted, in that case it is not returned to the pool. +/// +/// \tparam Type Object pool type. +template +class concurrent_thread_local_object_pool +{ + /// Pool type. + using queue_type = concurrent_queue, + concurrent_queue_policy::lockfree_mpmc, + concurrent_queue_wait_policy::sleep>; + +public: + /// \brief Creates a concurrent instance pool from a list of instances. + /// + /// \remark An assertion is triggered if the list of instances is empty or if one of the elements is a \c nullptr. + concurrent_thread_local_object_pool(std::vector> instances) : + main_thread_id(std::this_thread::get_id()), queue(instances.size()) + { + srsran_assert(!instances.empty(), "Pool is empty."); + srsran_assert(std::none_of(instances.begin(), instances.end(), [](auto& element) { return !element; }), + "Invalid instance in pool."); + + for (std::unique_ptr& instance : instances) { + queue.try_push(std::move(instance)); + } + } + + /// \brief Creates a concurrent instance pool with a specific number of elements. + /// + /// \tparam Args Parameter pack used to construct the pool elements. + /// \param[in] nof_elements Number of pool elements to instantiate. + /// \param[in] args Element creation arguments. + template + concurrent_thread_local_object_pool(unsigned nof_elements, const Args&... args) : queue(nof_elements) + { + for (unsigned i_elem = 0; i_elem != nof_elements; ++i_elem) { + bool success = queue.try_push(std::move(args)...); + report_error_if_not(success, "Failed to push element into the queue."); + } + } + + /// Default destructor - It triggers a failure if any instance has not been returned to que pool. + ~concurrent_thread_local_object_pool() + { + if (main_thread_ptr) { + report_error_if_not(queue.try_push(std::move(main_thread_ptr)), + "Failed to push element into the queue (destructor)."); + } + report_fatal_error_if_not(queue.size() == queue.capacity(), "Not all instances are returned to the pool."); + } + + /// Custom deleter that moves the ownership of the pointer back into the pool. + class custom_deleter + { + public: + custom_deleter(queue_type& queue_) : queue(&queue_) {} + + void operator()(Type* ptr) + { + // Ignore pointer if it is invalid. + if (ptr == nullptr) { + return; + } + + bool success = queue->push_blocking(std::unique_ptr(ptr)); + report_error_if_not(success, "Failed to push element into the queue (deleter)."); + } + + private: + queue_type* queue; + }; + + /// Specify unique pointer with custom deleter. + using unique_ptr = std::unique_ptr; + + /// \brief Gets an instance from the pool. + /// + /// The selected instance is fixed for the thread that calls the method. + Type& get() + { + // Checks if the thread is the main. + if (std::this_thread::get_id() == main_thread_id) { + if (!main_thread_ptr) { + report_fatal_error_if_not(queue.try_pop(main_thread_ptr), "Insufficient number of instances."); + } + return *main_thread_ptr; + } + + thread_local unique_ptr ptr = [this]() { + std::unique_ptr std_ptr; + report_fatal_error_if_not(queue.try_pop(std_ptr), "Insufficient number of instances."); + + unique_ptr custom_ptr(std_ptr.get(), custom_deleter(queue)); + + std_ptr.release(); + + return custom_ptr; + }(); + + return *ptr; + } + + /// Gets the pool capacity. + std::size_t capacity() const { return queue.capacity(); } + +private: + /// Main thread identifier. + std::thread::id main_thread_id; + /// Main thread pointer. + std::unique_ptr main_thread_ptr; + /// Actual pool. + queue_type queue; +}; + +} // namespace srsran diff --git a/include/srsran/support/srsran_assert.h b/include/srsran/support/srsran_assert.h index 866800b64d..6469ce136f 100644 --- a/include/srsran/support/srsran_assert.h +++ b/include/srsran/support/srsran_assert.h @@ -67,6 +67,8 @@ namespace detail { } // namespace srsran +// NOLINTBEGIN + /// Helper macro to log assertion message and terminate program. #define SRSRAN_ASSERT_FAILURE__(condmessage, fmtstr, ...) \ srsran::detail::print_and_abort( \ @@ -94,3 +96,5 @@ namespace detail { SRSRAN_ALWAYS_ASSERT_IFDEF__(PARANOID_ASSERTS_ENABLED, condition, fmtstr, ##__VA_ARGS__) #define srsran_assume(condition) static_cast((condition) ? void(0) : SRSRAN_UNREACHABLE) + +// NOLINTEND diff --git a/include/srsran/support/unique_thread.h b/include/srsran/support/unique_thread.h index e89b048f79..55e4a3c994 100644 --- a/include/srsran/support/unique_thread.h +++ b/include/srsran/support/unique_thread.h @@ -115,6 +115,23 @@ struct os_sched_affinity_bitmask { void set(size_t cpu_idx) { cpu_bitset.set(cpu_idx); } + /// \brief Finds, within a range of CPU indexes, the lowest CPU enabled. + /// \param[in] start_cpu_index Starting CPU index for the search. + /// \param[in] end_cpu_index End CPU index for the search. + /// \return Returns the lowest found bit index or -1 in case no bit was found with the provided value argument. + int find_lowest(size_t start_cpu_index, size_t end_cpu_index) + { + return cpu_bitset.find_lowest(start_cpu_index, end_cpu_index); + } + + /// Returns true if no CPU is enabled. + bool none() const { return cpu_bitset.none(); } + + /// \brief Fills range of bits to true. + /// \param[in] start Starting bit index that will be set. + /// \param[in] end End bit index (excluding) where the bits stop being set. + void fill(size_t start, size_t end) { cpu_bitset.fill(start, end); } + bool test(size_t cpu_idx) const { return cpu_bitset.test(cpu_idx); } bool any() const { return cpu_bitset.any(); } diff --git a/lib/asn1/asn1_utils.cpp b/lib/asn1/asn1_utils.cpp index 879f88fedc..8d4f8b937e 100644 --- a/lib/asn1/asn1_utils.cpp +++ b/lib/asn1/asn1_utils.cpp @@ -132,7 +132,9 @@ SRSASN_CODE bit_ref::pack(uint64_t val, uint32_t n_bits) srsran_assert(n_bits < 64, "Invalid number of bits passed to pack()"); while (n_bits > 0) { if (offset == 0) { - writer.append(0); + if (not writer.append(0)) { + return SRSASN_ERROR; + } } uint64_t mask = ((1ul << n_bits) - 1ul); // bitmap of n_bits ones. val = val & mask; diff --git a/lib/cu_cp/CMakeLists.txt b/lib/cu_cp/CMakeLists.txt index 258f50b3a8..d7854f024e 100644 --- a/lib/cu_cp/CMakeLists.txt +++ b/lib/cu_cp/CMakeLists.txt @@ -40,6 +40,7 @@ set(SOURCES routines/pdu_session_resource_modification_routine.cpp routines/ue_context_release_routine.cpp routines/reestablishment_context_modification_routine.cpp + routines/ue_removal_routine.cpp routines/mobility/mobility_helpers.cpp routines/mobility/inter_du_handover_routine.cpp routines/mobility/inter_ngran_node_n2_handover_routine.cpp diff --git a/lib/cu_cp/adapters/cu_cp_adapters.h b/lib/cu_cp/adapters/cu_cp_adapters.h index 7b9cd8a456..ce7e4b9762 100644 --- a/lib/cu_cp/adapters/cu_cp_adapters.h +++ b/lib/cu_cp/adapters/cu_cp_adapters.h @@ -23,19 +23,26 @@ #pragma once #include "../cu_cp_impl_interface.h" +#include "srsran/e1ap/cu_cp/e1ap_cu_cp.h" +#include "srsran/f1ap/cu_cp/f1ap_cu.h" #include "srsran/ngap/ngap.h" namespace srsran { namespace srs_cu_cp { /// Adapter between CU-CP and NGAP, to initialize connection procedures -class cu_cp_ngap_adapter : public cu_cp_ngap_control_notifier +class cu_cp_ngap_adapter : public cu_cp_ngap_control_notifier, public cu_cp_ngap_statistics_notifier { public: - void connect_ngap(ngap_connection_manager& ngap_conn_mng_, ngap_control_message_handler& ngap_ctrl_handler_) + void connect_ngap(ngap_connection_manager& ngap_conn_mng_, + ngap_control_message_handler& ngap_ctrl_handler_, + ngap_ue_context_removal_handler& ngap_ue_handler_, + ngap_statistics_handler& ngap_statistic_handler_) { - ngap_conn_mng = &ngap_conn_mng_; - ngap_ctrl_handler = &ngap_ctrl_handler_; + ngap_conn_mng = &ngap_conn_mng_; + ngap_ctrl_handler = &ngap_ctrl_handler_; + ngap_ue_handler = &ngap_ue_handler_; + ngap_statistic_handler = &ngap_statistic_handler_; } async_task on_ng_setup_request(const ng_setup_request& request) override @@ -50,13 +57,115 @@ class cu_cp_ngap_adapter : public cu_cp_ngap_control_notifier return ngap_ctrl_handler->handle_ue_context_release_request(request); } + void remove_ue(ue_index_t ue_index) override + { + srsran_assert(ngap_ue_handler != nullptr, "NGAP handler must not be nullptr"); + return ngap_ue_handler->remove_ue_context(ue_index); + } + + size_t get_nof_ues() const override + { + srsran_assert(ngap_statistic_handler != nullptr, "NGAP statistics handler must not be nullptr"); + return ngap_statistic_handler->get_nof_ues(); + } + +private: + ngap_connection_manager* ngap_conn_mng = nullptr; + ngap_control_message_handler* ngap_ctrl_handler = nullptr; + ngap_ue_context_removal_handler* ngap_ue_handler = nullptr; + ngap_statistics_handler* ngap_statistic_handler = nullptr; +}; + +/// Adapter between CU-CP and E1AP to request UE removal +class cu_cp_e1ap_adapter : public cu_cp_e1ap_ue_removal_notifier, public cu_cp_e1ap_statistics_notifier +{ +public: + cu_cp_e1ap_adapter() = default; + + void connect_e1ap(e1ap_bearer_context_removal_handler& bearer_context_removal_, + e1ap_statistics_handler& e1ap_statistic_handler_) + { + bearer_context_removal = &bearer_context_removal_; + e1ap_statistic_handler = &e1ap_statistic_handler_; + } + + void remove_ue(ue_index_t ue_index) override + { + srsran_assert(bearer_context_removal != nullptr, "E1AP bearer context removal handler must not be nullptr"); + bearer_context_removal->remove_bearer_context(ue_index); + } + + size_t get_nof_ues() const override + { + srsran_assert(e1ap_statistic_handler != nullptr, "E1AP statistics handler must not be nullptr"); + return e1ap_statistic_handler->get_nof_ues(); + } + +private: + e1ap_bearer_context_removal_handler* bearer_context_removal = nullptr; + e1ap_statistics_handler* e1ap_statistic_handler = nullptr; +}; + +/// Adapter between CU-CP and F1AP to request UE removal +class cu_cp_f1ap_adapter : public cu_cp_f1ap_ue_removal_notifier, public cu_cp_e1ap_statistics_notifier +{ +public: + cu_cp_f1ap_adapter() = default; + + void connect_f1ap(f1ap_ue_context_removal_handler& f1ap_ue_handler_, f1ap_statistics_handler& f1ap_statistic_handler_) + { + f1ap_ue_handler = &f1ap_ue_handler_; + f1ap_statistic_handler = &f1ap_statistic_handler_; + } + + void remove_ue(ue_index_t ue_index) override + { + srsran_assert(f1ap_ue_handler != nullptr, "F1AP handler must not be nullptr"); + f1ap_ue_handler->remove_ue_context(ue_index); + } + + size_t get_nof_ues() const override + { + srsran_assert(f1ap_statistic_handler != nullptr, "F1AP statistics handler must not be nullptr"); + return f1ap_statistic_handler->get_nof_ues(); + } + +private: + f1ap_ue_context_removal_handler* f1ap_ue_handler = nullptr; + f1ap_statistics_handler* f1ap_statistic_handler = nullptr; +}; + +/// Adapter between CU-CP and RRC DU to request UE removal +class cu_cp_rrc_du_adapter : public cu_cp_rrc_ue_removal_notifier, public cu_cp_rrc_du_statistics_notifier +{ +public: + cu_cp_rrc_du_adapter() = default; + + void connect_rrc_du(rrc_ue_removal_handler& ue_removal_handler_, rrc_du_statistics_handler& rrc_du_statistic_handler_) + { + ue_removal_handler = &ue_removal_handler_; + rrc_du_statistic_handler = &rrc_du_statistic_handler_; + } + + void remove_ue(ue_index_t ue_index) override + { + srsran_assert(ue_removal_handler != nullptr, "RRC UE removal handler must not be nullptr"); + ue_removal_handler->remove_ue(ue_index); + } + + size_t get_nof_ues() const override + { + srsran_assert(rrc_du_statistic_handler != nullptr, "RRC DU statistics handler must not be nullptr"); + return rrc_du_statistic_handler->get_nof_ues(); + } + private: - ngap_connection_manager* ngap_conn_mng = nullptr; - ngap_control_message_handler* ngap_ctrl_handler = nullptr; + rrc_ue_removal_handler* ue_removal_handler = nullptr; + rrc_du_statistics_handler* rrc_du_statistic_handler = nullptr; }; /// Adapter between CU-CP and RRC UE, to transfer UE context e.g. for RRC Reestablishments -class cu_cp_rrc_ue_adapter : public cu_cp_rrc_ue_context_trasfer_notifier +class cu_cp_rrc_ue_adapter : public cu_cp_rrc_ue_context_transfer_notifier { public: cu_cp_rrc_ue_adapter() = default; diff --git a/lib/cu_cp/adapters/du_processor_adapters.h b/lib/cu_cp/adapters/du_processor_adapters.h index 5fef60caa4..64f07f825a 100644 --- a/lib/cu_cp/adapters/du_processor_adapters.h +++ b/lib/cu_cp/adapters/du_processor_adapters.h @@ -73,21 +73,42 @@ class du_processor_to_cu_cp_task_scheduler : public du_processor_ue_task_schedul class du_processor_cu_cp_adapter : public du_processor_cu_cp_notifier { public: - void connect_cu_cp(cu_cp_du_event_handler& cu_cp_mng_, ngap_du_processor_control_notifier& ngap_du_notifier_) + void connect_cu_cp(cu_cp_du_event_handler& cu_cp_mng_, + cu_cp_ue_removal_handler& ue_removal_handler_, + ngap_du_processor_control_notifier& ngap_du_notifier_) { - cu_cp_handler = &cu_cp_mng_; - ngap_du_notifier = &ngap_du_notifier_; + cu_cp_handler = &cu_cp_mng_; + ue_removal_handler = &ue_removal_handler_; + ngap_du_notifier = &ngap_du_notifier_; } - void on_rrc_ue_created(du_index_t du_index, ue_index_t ue_index, rrc_ue_interface& rrc_ue) override + void on_du_processor_created(du_index_t du_index, + f1ap_ue_context_removal_handler& f1ap_handler, + f1ap_statistics_handler& f1ap_statistic_handler, + rrc_ue_removal_handler& rrc_handler, + rrc_du_statistics_handler& rrc_statistic_handler) override { srsran_assert(cu_cp_handler != nullptr, "CU-CP handler must not be nullptr"); - cu_cp_handler->handle_rrc_ue_creation(du_index, ue_index, rrc_ue, *ngap_du_notifier); + cu_cp_handler->handle_du_processor_creation( + du_index, f1ap_handler, f1ap_statistic_handler, rrc_handler, rrc_statistic_handler); + } + + void on_rrc_ue_created(ue_index_t ue_index, rrc_ue_interface& rrc_ue) override + { + srsran_assert(cu_cp_handler != nullptr, "CU-CP handler must not be nullptr"); + cu_cp_handler->handle_rrc_ue_creation(ue_index, rrc_ue, *ngap_du_notifier); + } + + void on_ue_removal_required(ue_index_t ue_index) override + { + srsran_assert(ue_removal_handler != nullptr, "CU-CP UE Removal handler must not be nullptr"); + ue_removal_handler->handle_ue_removal_request(ue_index); } private: - cu_cp_du_event_handler* cu_cp_handler = nullptr; - ngap_du_processor_control_notifier* ngap_du_notifier = nullptr; + cu_cp_du_event_handler* cu_cp_handler = nullptr; + cu_cp_ue_removal_handler* ue_removal_handler = nullptr; + ngap_du_processor_control_notifier* ngap_du_notifier = nullptr; }; /// Adapter between DU processor and E1AP @@ -147,13 +168,19 @@ class du_processor_f1ap_ue_context_adapter : public du_processor_f1ap_ue_context return handler->handle_ue_context_release_command(msg); } - virtual async_task + async_task on_ue_context_modification_request(const f1ap_ue_context_modification_request& request) override { srsran_assert(handler != nullptr, "F1AP handler must not be nullptr"); return handler->handle_ue_context_modification_request(request); } + bool on_intra_du_reestablishment(ue_index_t ue_index, ue_index_t old_ue_index) override + { + srsran_assert(handler != nullptr, "F1AP handler must not be nullptr"); + return handler->handle_ue_id_update(ue_index, old_ue_index); + } + private: f1ap_ue_context_manager* handler = nullptr; }; @@ -204,12 +231,6 @@ class du_processor_rrc_du_adapter : public du_processor_rrc_du_ue_notifier return rrc_du_handler->add_ue(resource_mng, msg); } - virtual void on_ue_context_release_command(ue_index_t ue_index) override - { - srsran_assert(rrc_du_handler != nullptr, "RRC DU UE handler must not be nullptr"); - return rrc_du_handler->remove_ue(ue_index); - } - virtual void on_release_ues() override { srsran_assert(rrc_du_handler != nullptr, "RRC DU UE handler must not be nullptr"); diff --git a/lib/cu_cp/adapters/e1ap_adapters.h b/lib/cu_cp/adapters/e1ap_adapters.h index 153a0aec83..c8eeaba0a9 100644 --- a/lib/cu_cp/adapters/e1ap_adapters.h +++ b/lib/cu_cp/adapters/e1ap_adapters.h @@ -56,10 +56,14 @@ class e1ap_cu_cp_adapter : public e1ap_cu_cp_notifier /// \brief Notifies about the creation of an E1AP. /// \param[in] bearer_context_manager The E1AP Bearer Context Manager interface. - void on_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager) override + /// \param[in] bearer_removal_handler The E1AP bearer context removal handler. + /// \param[in] e1ap_statistic_handler The E1AP statistic interface. + void on_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager, + e1ap_bearer_context_removal_handler& bearer_removal_handler, + e1ap_statistics_handler& e1ap_statistic_handler) override { srsran_assert(cu_cp_handler != nullptr, "E1AP handler must not be nullptr"); - cu_cp_handler->handle_e1ap_created(bearer_context_manager); + cu_cp_handler->handle_e1ap_created(bearer_context_manager, bearer_removal_handler, e1ap_statistic_handler); } void on_bearer_context_inactivity_notification_received(const cu_cp_inactivity_notification& msg) override diff --git a/lib/cu_cp/adapters/f1ap_adapters.h b/lib/cu_cp/adapters/f1ap_adapters.h index afa27b3311..20331825b6 100644 --- a/lib/cu_cp/adapters/f1ap_adapters.h +++ b/lib/cu_cp/adapters/f1ap_adapters.h @@ -23,6 +23,7 @@ #pragma once #include "../../f1ap/common/asn1_helpers.h" +#include "../cu_cp_impl_interface.h" #include "srsran/cu_cp/cu_cp.h" #include "srsran/cu_cp/du_processor.h" #include "srsran/f1ap/cu_cp/f1ap_cu.h" @@ -31,11 +32,27 @@ namespace srsran { namespace srs_cu_cp { -/// Adapter between F1AP and CU-CP, to handle DU specific procedure outcomes (e.g. F1 Remove) -class f1ap_cu_cp_adapter : public f1ap_du_management_notifier +/// Adapter between F1AP and CU-CP +class f1ap_cu_cp_adapter : public f1ap_ue_removal_notifier { public: - void connect_cu_cp(du_repository& cu_cp_mng_) { du_handler = &cu_cp_mng_; } + void connect_cu_cp(cu_cp_ue_removal_handler& ue_removal_handler_) { ue_removal_handler = &ue_removal_handler_; } + + void on_ue_removal_required(ue_index_t ue_index) override + { + srsran_assert(ue_removal_handler != nullptr, "CU-CP UE removal handler must not be nullptr"); + return ue_removal_handler->handle_ue_removal_request(ue_index); + } + +private: + cu_cp_ue_removal_handler* ue_removal_handler = nullptr; +}; + +/// Adapter between F1AP and DU repository, to handle DU specific procedure outcomes (e.g. F1 Remove) +class f1ap_du_repository_adapter : public f1ap_du_management_notifier +{ +public: + void connect_du_repository(du_repository& du_handler_) { du_handler = &du_handler_; } void on_du_remove_request_received(const du_index_t du_index) override { diff --git a/lib/cu_cp/adapters/ngap_adapters.h b/lib/cu_cp/adapters/ngap_adapters.h index 8ab378537a..6403d98686 100644 --- a/lib/cu_cp/adapters/ngap_adapters.h +++ b/lib/cu_cp/adapters/ngap_adapters.h @@ -46,7 +46,7 @@ class ngap_to_cu_cp_task_scheduler : public ngap_ue_task_scheduler void schedule_async_task(ue_index_t ue_index, async_task&& task) override { srsran_assert(cu_cp_task_sched != nullptr, "CU-CP task scheduler handler must not be nullptr"); - logger.debug("ue={} Scheduling async task", ue_index); + logger.debug("ue={}: Scheduling async task", ue_index); cu_cp_task_sched->handle_ue_async_task(ue_index, std::move(task)); } @@ -123,39 +123,23 @@ class ngap_rrc_ue_adapter : public ngap_rrc_ue_pdu_notifier, public ngap_rrc_ue_ void connect_rrc_ue(rrc_dl_nas_message_handler* rrc_ue_msg_handler_, rrc_ue_init_security_context_handler* rrc_ue_security_handler_, - rrc_ue_handover_preparation_handler* rrc_ue_ho_prep_handler_, - up_resource_manager* up_manager_) + rrc_ue_handover_preparation_handler* rrc_ue_ho_prep_handler_) { rrc_ue_msg_handler = rrc_ue_msg_handler_; rrc_ue_security_handler = rrc_ue_security_handler_; rrc_ue_ho_prep_handler = rrc_ue_ho_prep_handler_; - up_manager = up_manager_; } void on_new_pdu(byte_buffer nas_pdu) override { srsran_assert(rrc_ue_msg_handler != nullptr, "RRC UE message handler must not be nullptr"); - - dl_nas_transport_message dl_nas_msg = {}; - dl_nas_msg.nas_pdu = std::move(nas_pdu); - - rrc_ue_msg_handler->handle_dl_nas_transport_message(dl_nas_msg); + rrc_ue_msg_handler->handle_dl_nas_transport_message(std::move(nas_pdu)); } - async_task on_new_security_context(const asn1::ngap::ue_security_cap_s& caps, - const asn1::fixed_bitstring<256, false, true>& key) override + async_task on_new_security_context(const security::security_context& sec_context) override { srsran_assert(rrc_ue_security_handler != nullptr, "RRC UE security handler must not be nullptr"); - - security::security_context sec_ctxt; - copy_asn1_key(sec_ctxt.k, key); - fill_supported_algorithms(sec_ctxt.supported_int_algos, caps.nr_integrity_protection_algorithms); - fill_supported_algorithms(sec_ctxt.supported_enc_algos, caps.nr_encryption_algorithms); - logger.debug(key.data(), 32, "K_gnb"); - logger.debug("Supported integrity algorithms: {}", sec_ctxt.supported_int_algos); - logger.debug("Supported ciphering algorithms: {}", sec_ctxt.supported_enc_algos); - - return rrc_ue_security_handler->handle_init_security_context(sec_ctxt); + return rrc_ue_security_handler->handle_init_security_context(sec_context); } bool on_security_enabled() override @@ -164,25 +148,10 @@ class ngap_rrc_ue_adapter : public ngap_rrc_ue_pdu_notifier, public ngap_rrc_ue_ return rrc_ue_security_handler->get_security_enabled(); } - /// \brief Get required context for inter-gNB handover. - ngap_ue_source_handover_context on_ue_source_handover_context_required() override + byte_buffer on_handover_preparation_message_required() override { srsran_assert(rrc_ue_ho_prep_handler != nullptr, "RRC UE up manager must not be nullptr"); - ngap_ue_source_handover_context src_ctx; - const std::map& pdu_sessions = up_manager->get_pdu_sessions_map(); - // create a map of all PDU sessions and their associated QoS flows - for (const auto& pdu_session : pdu_sessions) { - std::vector qos_flows; - for (const auto& drb : pdu_session.second.drbs) { - for (const auto& qos_flow : drb.second.qos_flows) { - qos_flows.push_back(qos_flow.first); - } - } - src_ctx.pdu_sessions.insert({pdu_session.first, qos_flows}); - } - src_ctx.rrc_container = rrc_ue_ho_prep_handler->get_packed_handover_preparation_message(); - - return src_ctx; + return rrc_ue_ho_prep_handler->get_packed_handover_preparation_message(); } bool on_new_rrc_handover_command(byte_buffer cmd) override @@ -195,8 +164,6 @@ class ngap_rrc_ue_adapter : public ngap_rrc_ue_pdu_notifier, public ngap_rrc_ue_ rrc_dl_nas_message_handler* rrc_ue_msg_handler = nullptr; rrc_ue_init_security_context_handler* rrc_ue_security_handler = nullptr; rrc_ue_handover_preparation_handler* rrc_ue_ho_prep_handler = nullptr; - up_resource_manager* up_manager = nullptr; - srslog::basic_logger& logger = srslog::fetch_basic_logger("NGAP"); }; /// Adapter between NGAP and DU Processor @@ -220,7 +187,6 @@ class ngap_du_processor_adapter : public ngap_du_processor_control_notifier on_new_pdu_session_resource_setup_request(cu_cp_pdu_session_resource_setup_request& request) override { srsran_assert(du_processor_ngap_handler != nullptr, "DU Processor handler must not be nullptr"); - return du_processor_ngap_handler->handle_new_pdu_session_resource_setup_request(request); } @@ -228,7 +194,6 @@ class ngap_du_processor_adapter : public ngap_du_processor_control_notifier on_new_pdu_session_resource_modify_request(cu_cp_pdu_session_resource_modify_request& request) override { srsran_assert(du_processor_ngap_handler != nullptr, "DU Processor handler must not be nullptr"); - return du_processor_ngap_handler->handle_new_pdu_session_resource_modify_request(request); } @@ -236,7 +201,6 @@ class ngap_du_processor_adapter : public ngap_du_processor_control_notifier on_new_pdu_session_resource_release_command(cu_cp_pdu_session_resource_release_command& command) override { srsran_assert(du_processor_ngap_handler != nullptr, "DU Processor handler must not be nullptr"); - return du_processor_ngap_handler->handle_new_pdu_session_resource_release_command(command); } @@ -244,7 +208,6 @@ class ngap_du_processor_adapter : public ngap_du_processor_control_notifier on_new_ue_context_release_command(const cu_cp_ngap_ue_context_release_command& command) override { srsran_assert(du_processor_ngap_handler != nullptr, "DU Processor handler must not be nullptr"); - return du_processor_ngap_handler->handle_ue_context_release_command(command); } diff --git a/lib/cu_cp/adapters/rrc_ue_adapters.h b/lib/cu_cp/adapters/rrc_ue_adapters.h index e84f865dd0..84fc9abfc7 100644 --- a/lib/cu_cp/adapters/rrc_ue_adapters.h +++ b/lib/cu_cp/adapters/rrc_ue_adapters.h @@ -41,11 +41,10 @@ class rrc_ue_f1ap_pdu_adapter : public rrc_pdu_f1ap_notifier { } - void on_new_rrc_pdu(const srb_id_t srb_id, const byte_buffer& pdu, ue_index_t old_ue_index) override + void on_new_rrc_pdu(const srb_id_t srb_id, const byte_buffer& pdu) override { f1ap_dl_rrc_message f1ap_msg = {}; f1ap_msg.ue_index = ue_index; - f1ap_msg.old_ue_index = old_ue_index; f1ap_msg.srb_id = srb_id; f1ap_msg.rrc_container = pdu.copy(); f1ap_handler.handle_dl_rrc_message_transfer(f1ap_msg); @@ -66,7 +65,7 @@ class rrc_ue_du_processor_adapter : public rrc_ue_du_processor_notifier } async_task - on_ue_context_release_command(const rrc_ue_context_release_command& cmd) override + on_ue_context_release_command(const cu_cp_ue_context_release_command& cmd) override { srsran_assert(du_processor_rrc_ue_handler != nullptr, "DU processor handler must not be nullptr"); return du_processor_rrc_ue_handler->handle_ue_context_release_command(cmd); @@ -131,39 +130,16 @@ class rrc_ue_ngap_adapter : public rrc_ue_nas_notifier, public rrc_ue_control_no ngap_ctrl_msg_handler = &ngap_ctrl_msg_handler_; } - void on_initial_ue_message(const initial_ue_message& msg) override + void on_initial_ue_message(const cu_cp_initial_ue_message& msg) override { srsran_assert(ngap_nas_msg_handler != nullptr, "NGAP handler must not be nullptr"); - - ngap_initial_ue_message ngap_init_ue_msg; - ngap_init_ue_msg.ue_index = msg.ue_index; - ngap_init_ue_msg.nas_pdu = msg.nas_pdu.copy(); - - ngap_init_ue_msg.establishment_cause.value = - rrc_establishment_cause_to_ngap_rrcestablishment_cause(msg.establishment_cause).value; - - ngap_init_ue_msg.nr_cgi.nr_cell_id.from_number(msg.cell.cgi.nci); - ngap_init_ue_msg.nr_cgi.plmn_id.from_string(msg.cell.cgi.plmn_hex); - ngap_init_ue_msg.tac = msg.cell.tac; - - ngap_init_ue_msg.five_g_s_tmsi = msg.five_g_s_tmsi; - - ngap_nas_msg_handler->handle_initial_ue_message(ngap_init_ue_msg); + ngap_nas_msg_handler->handle_initial_ue_message(msg); } - void on_ul_nas_transport_message(const ul_nas_transport_message& msg) override + void on_ul_nas_transport_message(const cu_cp_ul_nas_transport& msg) override { srsran_assert(ngap_nas_msg_handler != nullptr, "NGAP handler must not be nullptr"); - - ngap_ul_nas_transport_message ngap_ul_nas_msg; - ngap_ul_nas_msg.ue_index = msg.ue_index; - ngap_ul_nas_msg.nas_pdu = msg.nas_pdu.copy(); - - ngap_ul_nas_msg.nr_cgi.nr_cell_id.from_number(msg.cell.cgi.nci); - ngap_ul_nas_msg.nr_cgi.plmn_id.from_string(msg.cell.cgi.plmn_hex); - ngap_ul_nas_msg.tac = msg.cell.tac; - - ngap_nas_msg_handler->handle_ul_nas_transport_message(ngap_ul_nas_msg); + ngap_nas_msg_handler->handle_ul_nas_transport_message(msg); } void on_ue_context_release_request(const cu_cp_ue_context_release_request& msg) override @@ -185,59 +161,17 @@ class rrc_ue_ngap_adapter : public rrc_ue_nas_notifier, public rrc_ue_control_no private: ngap_nas_message_handler* ngap_nas_msg_handler = nullptr; ngap_control_message_handler* ngap_ctrl_msg_handler = nullptr; - - /// \brief Convert a RRC Establishment Cause to a NGAP RRC Establishment Cause. - /// \param establishment_cause The RRC Establishment Cause. - /// \return The NGAP RRC Establishment Cause. - inline asn1::ngap::rrc_establishment_cause_opts rrc_establishment_cause_to_ngap_rrcestablishment_cause( - const asn1::rrc_nr::establishment_cause_opts& establishment_cause) - { - asn1::ngap::rrc_establishment_cause_opts rrcestablishment_cause = {}; - switch (establishment_cause.value) { - case asn1::rrc_nr::establishment_cause_opts::options::emergency: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::emergency; - break; - case asn1::rrc_nr::establishment_cause_opts::options::high_prio_access: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::high_prio_access; - break; - case asn1::rrc_nr::establishment_cause_opts::options::mt_access: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::mt_access; - break; - case asn1::rrc_nr::establishment_cause_opts::options::mo_sig: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::mo_sig; - break; - case asn1::rrc_nr::establishment_cause_opts::options::mo_data: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::mo_data; - break; - case asn1::rrc_nr::establishment_cause_opts::options::mo_voice_call: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::mo_voice_call; - break; - case asn1::rrc_nr::establishment_cause_opts::options::mo_video_call: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::mo_video_call; - break; - case asn1::rrc_nr::establishment_cause_opts::options::mo_sms: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::mo_sms; - break; - case asn1::rrc_nr::establishment_cause_opts::options::mps_prio_access: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::mps_prio_access; - break; - case asn1::rrc_nr::establishment_cause_opts::options::mcs_prio_access: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::mcs_prio_access; - break; - default: - rrcestablishment_cause.value = asn1::ngap::rrc_establishment_cause_opts::nulltype; - break; - } - - return rrcestablishment_cause; - } }; /// Adapter between RRC UE and CU-CP class rrc_ue_cu_cp_adapter : public rrc_ue_reestablishment_notifier { public: - void connect_cu_cp(cu_cp_rrc_ue_interface& cu_cp_rrc_ue_) { cu_cp_rrc_ue_handler = &cu_cp_rrc_ue_; } + void connect_cu_cp(cu_cp_rrc_ue_interface& cu_cp_rrc_ue_, cu_cp_ue_removal_handler& ue_removal_handler_) + { + cu_cp_rrc_ue_handler = &cu_cp_rrc_ue_; + ue_removal_handler = &ue_removal_handler_; + } rrc_reestablishment_ue_context_t on_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, ue_index_t ue_index) override @@ -246,14 +180,21 @@ class rrc_ue_cu_cp_adapter : public rrc_ue_reestablishment_notifier return cu_cp_rrc_ue_handler->handle_rrc_reestablishment_request(old_pci, old_c_rnti, ue_index); } - void on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) override + async_task on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) override { srsran_assert(cu_cp_rrc_ue_handler != nullptr, "CU-CP handler must not be nullptr"); return cu_cp_rrc_ue_handler->handle_ue_context_transfer(ue_index, old_ue_index); } + void on_ue_removal_required(ue_index_t ue_index) override + { + srsran_assert(ue_removal_handler != nullptr, "CU-CP UE removal handler must not be nullptr"); + return ue_removal_handler->handle_ue_removal_request(ue_index); + } + private: - cu_cp_rrc_ue_interface* cu_cp_rrc_ue_handler = nullptr; + cu_cp_rrc_ue_interface* cu_cp_rrc_ue_handler = nullptr; + cu_cp_ue_removal_handler* ue_removal_handler = nullptr; }; } // namespace srs_cu_cp diff --git a/lib/cu_cp/cu_cp_impl.cpp b/lib/cu_cp/cu_cp_impl.cpp index c3b7aa5b80..edb115ae2e 100644 --- a/lib/cu_cp/cu_cp_impl.cpp +++ b/lib/cu_cp/cu_cp_impl.cpp @@ -25,7 +25,11 @@ #include "cu_up_processor/cu_up_processor_repository.h" #include "ue_manager_impl.h" #include "srsran/cu_cp/cu_cp_types.h" +#include "srsran/f1ap/cu_cp/f1ap_cu.h" #include "srsran/ngap/ngap_factory.h" +#include "srsran/rrc/rrc_du.h" +#include "srsran/support/async/event_sender_receiver.h" +#include #include using namespace srsran; @@ -45,8 +49,10 @@ cu_cp_impl::cu_cp_impl(const cu_cp_configuration& config_) : cell_meas_mng(create_cell_meas_manager(config_.mobility_config.meas_manager_config, cell_meas_ev_notifier)), du_db(du_repository_config{cfg, *this, + get_cu_cp_ue_removal_handler(), du_processor_e1ap_notifier, du_processor_ngap_notifier, + f1ap_cu_cp_notifier, rrc_ue_ngap_notifier, rrc_ue_ngap_notifier, rrc_ue_cu_cp_notifier, @@ -63,8 +69,9 @@ cu_cp_impl::cu_cp_impl(const cu_cp_configuration& config_) : // connect event notifiers to layers ngap_cu_cp_ev_notifier.connect_cu_cp(get_cu_cp_ngap_handler(), du_db); e1ap_ev_notifier.connect_cu_cp(get_cu_cp_e1ap_handler()); + f1ap_cu_cp_notifier.connect_cu_cp(get_cu_cp_ue_removal_handler()); cell_meas_ev_notifier.connect_mobility_manager(*mobility_mng.get()); - rrc_ue_cu_cp_notifier.connect_cu_cp(this->get_cu_cp_rrc_ue_interface()); + rrc_ue_cu_cp_notifier.connect_cu_cp(get_cu_cp_rrc_ue_interface(), get_cu_cp_ue_removal_handler()); // connect task schedulers ngap_task_sched.connect_cu_cp(ue_task_sched); @@ -74,12 +81,20 @@ cu_cp_impl::cu_cp_impl(const cu_cp_configuration& config_) : ngap_entity = create_ngap( cfg.ngap_config, ngap_cu_cp_ev_notifier, ngap_task_sched, ue_mng, *cfg.ngap_notifier, *cfg.cu_cp_executor); ngap_adapter.connect_ngap(ngap_entity->get_ngap_connection_manager(), - ngap_entity->get_ngap_control_message_handler()); + ngap_entity->get_ngap_control_message_handler(), + ngap_entity->get_ngap_ue_context_removal_handler(), + ngap_entity->get_ngap_statistics_handler()); du_processor_ngap_notifier.connect_ngap(ngap_entity->get_ngap_control_message_handler()); rrc_ue_ngap_notifier.connect_ngap(ngap_entity->get_ngap_nas_message_handler(), ngap_entity->get_ngap_control_message_handler()); - routine_mng = std::make_unique(ngap_adapter, ngap_cu_cp_ev_notifier); + routine_mng = std::make_unique(ngap_adapter, ngap_cu_cp_ev_notifier, ue_task_sched); + + // Start statistics report timer + statistics_report_timer = cfg.timers->create_unique_timer(*cfg.cu_cp_executor); + statistics_report_timer.set(cfg.statistics_report_period, + [this](timer_id_t /*tid*/) { on_statistics_report_timer_expired(); }); + statistics_report_timer.run(); } cu_cp_impl::~cu_cp_impl() @@ -131,10 +146,15 @@ ngap_event_handler& cu_cp_impl::get_ngap_event_handler() return *ngap_entity; } -void cu_cp_impl::handle_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager) +void cu_cp_impl::handle_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager, + e1ap_bearer_context_removal_handler& bearer_removal_handler, + e1ap_statistics_handler& e1ap_statistic_handler) { // Connect e1ap to DU processor du_processor_e1ap_notifier.connect_e1ap(bearer_context_manager); + // Connect e1ap to CU-CP + e1ap_adapters[uint_to_cu_up_index(0)] = {}; + e1ap_adapters.at(uint_to_cu_up_index(0)).connect_e1ap(bearer_removal_handler, e1ap_statistic_handler); } void cu_cp_impl::handle_bearer_context_inactivity_notification(const cu_cp_inactivity_notification& msg) @@ -188,23 +208,92 @@ cu_cp_impl::handle_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, return reest_context; } -void cu_cp_impl::handle_ue_context_transfer(ue_index_t ue_index, ue_index_t old_ue_index) +async_task cu_cp_impl::handle_ue_context_transfer(ue_index_t ue_index, ue_index_t old_ue_index) { - // Transfer NGAP UE Context to new UE and remove the old context - ue_mng.transfer_ngap_ue_context(ue_index, old_ue_index); - - // Transfer E1AP UE Context to new UE and remove old context - cu_up_db.get_cu_up(uint_to_cu_up_index(0)).update_ue_index(ue_index, old_ue_index); + // Task to run in old UE task scheduler. + auto handle_ue_context_transfer_impl = [this, ue_index, old_ue_index]() { + if (ue_mng.find_du_ue(old_ue_index) == nullptr) { + logger.warning("Old UE index={} got removed", old_ue_index); + return false; + } + + // Notify old F1AP UE context to F1AP. + if (get_du_index_from_ue_index(old_ue_index) == get_du_index_from_ue_index(ue_index)) { + const bool result = du_db.get_du(get_du_index_from_ue_index(old_ue_index)) + .get_f1ap_ue_context_notifier() + .on_intra_du_reestablishment(ue_index, old_ue_index); + if (not result) { + logger.warning("The F1AP UE context of the old UE index {} does not exist", old_ue_index); + return false; + } + + // Transfer NGAP UE Context to new UE and remove the old context + ngap_entity->update_ue_index(ue_index, old_ue_index); + + // Transfer E1AP UE Context to new UE and remove old context + cu_up_db.get_cu_up(uint_to_cu_up_index(0)).update_ue_index(ue_index, old_ue_index); + } + + return true; + }; + + // Task that the caller will use to sync with the old UE task scheduler. + struct transfer_context_task { + transfer_context_task(cu_cp_impl& parent_, ue_index_t old_ue_index_, unique_function callable) : + parent(parent_), + old_ue_index(old_ue_index_), + task([this, callable = std::move(callable)]() { transfer_successful = callable(); }) + { + } + + void operator()(coro_context>& ctx) + { + CORO_BEGIN(ctx); + + CORO_AWAIT_VALUE(const bool task_run, + parent.ue_task_sched.dispatch_and_await_task_completion(old_ue_index, std::move(task))); + + CORO_RETURN(task_run and transfer_successful); + } + + cu_cp_impl& parent; + ue_index_t old_ue_index; + unique_task task; + + bool transfer_successful = false; + }; + + return launch_async(*this, old_ue_index, handle_ue_context_transfer_impl); +} - // Remove old RRC UE and DU UE - ue_task_sched.handle_ue_async_task(old_ue_index, - du_db.request_ue_removal(get_du_index_from_ue_index(old_ue_index), old_ue_index)); +void cu_cp_impl::handle_ue_removal_request(ue_index_t ue_index) +{ + du_index_t du_index = get_du_index_from_ue_index(ue_index); + cu_up_index_t cu_up_index = uint_to_cu_up_index(0); // TODO: Update when mapping from UE index to CU-UP exists + routine_mng->start_ue_removal_routine(ue_index, + rrc_du_adapters.at(du_index), + e1ap_adapters.at(cu_up_index), + f1ap_adapters.at(du_index), + ngap_adapter, + ue_mng, + logger); } // private -void cu_cp_impl::handle_rrc_ue_creation(du_index_t du_index, - ue_index_t ue_index, +void cu_cp_impl::handle_du_processor_creation(du_index_t du_index, + f1ap_ue_context_removal_handler& f1ap_handler, + f1ap_statistics_handler& f1ap_statistic_handler, + rrc_ue_removal_handler& rrc_handler, + rrc_du_statistics_handler& rrc_statistic_handler) +{ + f1ap_adapters[du_index] = {}; + f1ap_adapters.at(du_index).connect_f1ap(f1ap_handler, f1ap_statistic_handler); + rrc_du_adapters[du_index] = {}; + rrc_du_adapters.at(du_index).connect_rrc_du(rrc_handler, rrc_statistic_handler); +} + +void cu_cp_impl::handle_rrc_ue_creation(ue_index_t ue_index, rrc_ue_interface& rrc_ue, ngap_du_processor_control_notifier& ngap_du_notifier) { @@ -214,10 +303,52 @@ void cu_cp_impl::handle_rrc_ue_creation(du_index_t du_i ngap_entity->create_ngap_ue(ue_index, rrc_ue_adapter, rrc_ue_adapter, ngap_du_notifier); rrc_ue_adapter.connect_rrc_ue(&rrc_ue.get_rrc_dl_nas_message_handler(), &rrc_ue.get_rrc_ue_init_security_context_handler(), - &rrc_ue.get_rrc_ue_handover_preparation_handler(), - &ue_mng.find_du_ue(ue_index)->get_up_resource_manager()); + &rrc_ue.get_rrc_ue_handover_preparation_handler()); // Create and connect cu-cp to rrc ue adapter cu_cp_rrc_ue_ev_notifiers[ue_index] = {}; cu_cp_rrc_ue_ev_notifiers.at(ue_index).connect_rrc_ue(rrc_ue.get_rrc_ue_context_handler()); } + +void cu_cp_impl::on_statistics_report_timer_expired() +{ + // Get number of F1AP UEs + unsigned nof_f1ap_ues = 0; + for (auto& f1ap_adapter_pair : f1ap_adapters) { + nof_f1ap_ues += f1ap_adapter_pair.second.get_nof_ues(); + } + + // Get number of RRC UEs + unsigned nof_rrc_ues = 0; + for (auto& rrc_du_adapter_pair : rrc_du_adapters) { + nof_rrc_ues += rrc_du_adapter_pair.second.get_nof_ues(); + } + + // Get number of NGAP UEs + unsigned nof_ngap_ues = 0; + nof_ngap_ues = ngap_adapter.get_nof_ues(); + + // Get number of E1AP UEs + unsigned nof_e1ap_ues = 0; + for (auto& e1ap_adapter_pair : e1ap_adapters) { + nof_e1ap_ues += e1ap_adapter_pair.second.get_nof_ues(); + } + + // Get number of CU-CP UEs + unsigned nof_cu_cp_ues = 0; + nof_cu_cp_ues = ue_mng.get_nof_ues(); + + // Log statistics + logger.debug("num_f1ap_ues={} num_rrc_ues={} num_ngap_ues={} num_e1ap_ues={} num_cu_cp_ues={}", + nof_f1ap_ues, + nof_rrc_ues, + nof_e1ap_ues, + nof_ngap_ues, + nof_e1ap_ues, + nof_cu_cp_ues); + + // Restart timer + statistics_report_timer.set(cfg.statistics_report_period, + [this](timer_id_t /*tid*/) { on_statistics_report_timer_expired(); }); + statistics_report_timer.run(); +} diff --git a/lib/cu_cp/cu_cp_impl.h b/lib/cu_cp/cu_cp_impl.h index c1f155a1f5..6fc4f13f3a 100644 --- a/lib/cu_cp/cu_cp_impl.h +++ b/lib/cu_cp/cu_cp_impl.h @@ -39,6 +39,7 @@ #include "ue_manager_impl.h" #include "srsran/cu_cp/cell_meas_manager.h" #include "srsran/cu_cp/cu_cp_configuration.h" +#include "srsran/cu_cp/cu_cp_types.h" #include "srsran/f1ap/cu_cp/f1ap_cu.h" #include #include @@ -66,7 +67,9 @@ class cu_cp_impl final : public cu_cp_interface, public cu_cp_impl_interface bool amf_is_connected() override { return amf_connected; }; // CU-UP handler - void handle_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager) override; + void handle_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager, + e1ap_bearer_context_removal_handler& bearer_removal_handler, + e1ap_statistics_handler& e1ap_statistic_handler) override; void handle_bearer_context_inactivity_notification(const cu_cp_inactivity_notification& msg) override; // NGAP connection handler @@ -75,8 +78,11 @@ class cu_cp_impl final : public cu_cp_interface, public cu_cp_impl_interface // RRC UE handler rrc_reestablishment_ue_context_t - handle_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, ue_index_t ue_index) override; - void handle_ue_context_transfer(ue_index_t ue_index, ue_index_t old_ue_index) override; + handle_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, ue_index_t ue_index) override; + async_task handle_ue_context_transfer(ue_index_t ue_index, ue_index_t old_ue_index) override; + + // cu_cp_ue_removal_interface + void handle_ue_removal_request(ue_index_t ue_index) override; // cu_cp interface du_repository& get_connected_dus() override { return du_db; } @@ -86,14 +92,22 @@ class cu_cp_impl final : public cu_cp_interface, public cu_cp_impl_interface cu_cp_ngap_connection_interface& get_cu_cp_ngap_connection_interface() override { return *this; } cu_cp_ngap_handler& get_cu_cp_ngap_handler() override { return *this; } cu_cp_rrc_ue_interface& get_cu_cp_rrc_ue_interface() override { return *this; } + cu_cp_ue_removal_handler& get_cu_cp_ue_removal_handler() override { return *this; } private: // Handling of DU events. - void handle_rrc_ue_creation(du_index_t du_index, - ue_index_t ue_index, + void handle_du_processor_creation(du_index_t du_index, + f1ap_ue_context_removal_handler& f1ap_handler, + f1ap_statistics_handler& f1ap_statistic_handler, + rrc_ue_removal_handler& rrc_handler, + rrc_du_statistics_handler& rrc_statistic_handler) override; + + void handle_rrc_ue_creation(ue_index_t ue_index, rrc_ue_interface& rrc_ue, ngap_du_processor_control_notifier& ngap_to_du_notifier) override; + void on_statistics_report_timer_expired(); + cu_cp_configuration cfg; // logger @@ -111,6 +125,15 @@ class cu_cp_impl final : public cu_cp_interface, public cu_cp_impl_interface // CU-CP to NGAP adapter cu_cp_ngap_adapter ngap_adapter; + // CU-CP to E1AP adapters + std::map e1ap_adapters; + + // CU-CP to F1AP adapters + std::map f1ap_adapters; + + // CU-CP to RRC DU adapters + std::map rrc_du_adapters; + // UE Task scheduler used by DU processors. du_processor_to_cu_cp_task_scheduler du_processor_task_sched; @@ -130,6 +153,9 @@ class cu_cp_impl final : public cu_cp_interface, public cu_cp_impl_interface ngap_to_cu_cp_task_scheduler ngap_task_sched; ngap_cu_cp_adapter ngap_cu_cp_ev_notifier; + // F1AP to CU-CP adapter + f1ap_cu_cp_adapter f1ap_cu_cp_notifier; + // RRC UE to CU-CP adapter rrc_ue_cu_cp_adapter rrc_ue_cu_cp_notifier; @@ -154,6 +180,8 @@ class cu_cp_impl final : public cu_cp_interface, public cu_cp_impl_interface std::unique_ptr routine_mng; std::atomic amf_connected = {false}; + + unique_timer statistics_report_timer; }; } // namespace srs_cu_cp diff --git a/lib/cu_cp/cu_cp_impl_interface.h b/lib/cu_cp/cu_cp_impl_interface.h index 3ea151de7f..ddb6baf8f2 100644 --- a/lib/cu_cp/cu_cp_impl_interface.h +++ b/lib/cu_cp/cu_cp_impl_interface.h @@ -24,7 +24,9 @@ #include "srsran/cu_cp/cu_cp_types.h" #include "srsran/e1ap/cu_cp/e1ap_cu_cp.h" +#include "srsran/f1ap/cu_cp/f1ap_cu.h" #include "srsran/ngap/ngap.h" +#include "srsran/rrc/rrc_du.h" #include "srsran/rrc/rrc_ue.h" #include @@ -44,6 +46,21 @@ class cu_cp_ngap_control_notifier /// \brief Notify the NGAP to request a UE release e.g. due to inactivity. /// \param[in] msg The UE Context Release Request. virtual void on_ue_context_release_request(const cu_cp_ue_context_release_request& request) = 0; + + /// \brief Remove the context of a UE at the NGAP. + /// \param[in] ue_index The index of the UE to remove. + virtual void remove_ue(ue_index_t ue_index) = 0; +}; + +/// Methods used by CU-CP to request NGAP statistics +class cu_cp_ngap_statistics_notifier +{ +public: + virtual ~cu_cp_ngap_statistics_notifier() = default; + + /// \brief Get the number of UEs registered at the NGAP. + /// \return The number of UEs. + virtual size_t get_nof_ues() const = 0; }; /// Interface for the NGAP to interface with the DU repository @@ -72,26 +89,106 @@ class cu_cp_e1ap_handler /// \brief Handle the creation of an E1AP. /// \param[in] bearer_context_manager The E1AP Bearer Context Manager interface. - virtual void handle_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager) = 0; + /// \param[in] bearer_removal_handler The E1AP bearer context removal handler. + /// \param[in] e1ap_statistic_handler The E1AP statistic interface. + virtual void handle_e1ap_created(e1ap_bearer_context_manager& bearer_context_manager, + e1ap_bearer_context_removal_handler& bearer_removal_handler, + e1ap_statistics_handler& e1ap_statistic_handler) = 0; /// \brief Handle the reception of an Bearer Context Inactivity Notification message. /// \param[in] msg The received Bearer Context Inactivity Notification message. virtual void handle_bearer_context_inactivity_notification(const cu_cp_inactivity_notification& msg) = 0; }; +/// Methods used by CU-CP to request removal of the UE context at the E1AP +class cu_cp_e1ap_ue_removal_notifier +{ +public: + virtual ~cu_cp_e1ap_ue_removal_notifier() = default; + + /// \brief Remove the context of a UE at the E1AP. + /// \param[in] ue_index The index of the UE to remove. + virtual void remove_ue(ue_index_t ue_index) = 0; +}; + +/// Methods used by CU-CP to request E1AP statistics +class cu_cp_e1ap_statistics_notifier +{ +public: + virtual ~cu_cp_e1ap_statistics_notifier() = default; + + /// \brief Get the number of UEs registered at the E1AP. + /// \return The number of UEs. + virtual size_t get_nof_ues() const = 0; +}; + +/// Methods used by CU-CP to request removal of the UE context at the F1AP +class cu_cp_f1ap_ue_removal_notifier +{ +public: + virtual ~cu_cp_f1ap_ue_removal_notifier() = default; + + /// \brief Remove the context of a UE at the F1AP. + /// \param[in] ue_index The index of the UE to remove. + virtual void remove_ue(ue_index_t ue_index) = 0; +}; + +/// Methods used by CU-CP to request F1AP statistics +class cu_cp_f1ap_statistics_notifier +{ +public: + virtual ~cu_cp_f1ap_statistics_notifier() = default; + + /// \brief Get the number of UEs registered at the F1AP. + /// \return The number of UEs. + virtual size_t get_nof_ues() const = 0; +}; + +/// Methods used by CU-CP to request removal of the RRC UE context at the RRD DU +class cu_cp_rrc_ue_removal_notifier +{ +public: + virtual ~cu_cp_rrc_ue_removal_notifier() = default; + + /// \brief Remove the context of a UE at the RRC DU. + /// \param[in] ue_index The index of the UE to remove. + virtual void remove_ue(ue_index_t ue_index) = 0; +}; + +/// Methods used by CU-CP to request RRC DU statistics +class cu_cp_rrc_du_statistics_notifier +{ +public: + virtual ~cu_cp_rrc_du_statistics_notifier() = default; + + /// \brief Get the number of UEs registered at the RRC DU. + /// \return The number of UEs. + virtual size_t get_nof_ues() const = 0; +}; + /// Interface used to handle DU specific procedures class cu_cp_du_event_handler { public: virtual ~cu_cp_du_event_handler() = default; - /// \brief Handle a RRC UE creation notification from the DU processor. + /// \brief Handle about a successful F1AP and RRC creation. /// \param[in] du_index The index of the DU the UE is connected to. + /// \param[in] f1ap_handler Handler to the F1AP to initiate the UE context removal. + /// \param[in] f1ap_statistic_handler Handler to the F1AP statistic interface. + /// \param[in] rrc_handler Handler to the RRC DU to initiate the RRC UE removal. + /// \param[in] rrc_statistic_handler Handler to the RRC DU statistic interface. + virtual void handle_du_processor_creation(du_index_t du_index, + f1ap_ue_context_removal_handler& f1ap_handler, + f1ap_statistics_handler& f1ap_statistic_handler, + rrc_ue_removal_handler& rrc_handler, + rrc_du_statistics_handler& rrc_statistic_handler) = 0; + + /// \brief Handle a RRC UE creation notification from the DU processor. /// \param[in] ue_index The index of the UE. /// \param[in] rrc_ue The interface of the created RRC UE. /// \param[in] ngap_to_du_ev_notifier The notifier used by the NGAP to signal DU-specific events. - virtual void handle_rrc_ue_creation(du_index_t du_index, - ue_index_t ue_index, + virtual void handle_rrc_ue_creation(ue_index_t ue_index, rrc_ue_interface& rrc_ue, ngap_du_processor_control_notifier& ngap_to_du_ev_notifier) = 0; }; @@ -113,20 +210,20 @@ class cu_cp_rrc_ue_interface /// \brief Transfer and remove UE contexts for an ongoing Reestablishment. /// \param[in] ue_index The new UE index of the UE that sent the Reestablishment Request. /// \param[in] old_ue_index The old UE index of the UE that sent the Reestablishment Request. - virtual void handle_ue_context_transfer(ue_index_t ue_index, ue_index_t old_ue_index) = 0; + virtual async_task handle_ue_context_transfer(ue_index_t ue_index, ue_index_t old_ue_index) = 0; }; /// Methods used by CU-CP to transfer the RRC UE context e.g. for RRC Reestablishments -class cu_cp_rrc_ue_context_trasfer_notifier +class cu_cp_rrc_ue_context_transfer_notifier { public: - virtual ~cu_cp_rrc_ue_context_trasfer_notifier() = default; + virtual ~cu_cp_rrc_ue_context_transfer_notifier() = default; /// \brief Notifies the RRC UE to return the RRC Reestablishment UE context. virtual rrc_reestablishment_ue_context_t on_rrc_ue_context_transfer() = 0; }; -/// Interface for to request handover. +/// Interface to request handover. class cu_cp_mobility_manager_handler { public: @@ -138,13 +235,28 @@ class cu_cp_mobility_manager_handler virtual void handle_inter_du_handover_request(ue_index_t ue_index, pci_t target_pci) = 0; }; -class cu_cp_impl_interface : public cu_cp_e1ap_handler, public cu_cp_du_event_handler, public cu_cp_rrc_ue_interface +/// Interface to handle ue removals +class cu_cp_ue_removal_handler +{ +public: + virtual ~cu_cp_ue_removal_handler() = default; + + /// \brief Completly remove a UE from the CU-CP. + /// \param[in] ue_index The index of the UE to remove. + virtual void handle_ue_removal_request(ue_index_t ue_index) = 0; +}; + +class cu_cp_impl_interface : public cu_cp_e1ap_handler, + public cu_cp_du_event_handler, + public cu_cp_rrc_ue_interface, + public cu_cp_ue_removal_handler { public: virtual ~cu_cp_impl_interface() = default; - virtual cu_cp_e1ap_handler& get_cu_cp_e1ap_handler() = 0; - virtual cu_cp_rrc_ue_interface& get_cu_cp_rrc_ue_interface() = 0; + virtual cu_cp_e1ap_handler& get_cu_cp_e1ap_handler() = 0; + virtual cu_cp_rrc_ue_interface& get_cu_cp_rrc_ue_interface() = 0; + virtual cu_cp_ue_removal_handler& get_cu_cp_ue_removal_handler() = 0; virtual void start() = 0; }; diff --git a/lib/cu_cp/cu_up_processor/cu_up_processor_impl.h b/lib/cu_cp/cu_up_processor/cu_up_processor_impl.h index 49bed940ce..39e9a5357a 100644 --- a/lib/cu_cp/cu_up_processor/cu_up_processor_impl.h +++ b/lib/cu_cp/cu_up_processor/cu_up_processor_impl.h @@ -29,7 +29,7 @@ #include "srsran/cu_cp/cu_cp_types.h" #include "srsran/cu_cp/cu_up_processor_config.h" #include "srsran/e1ap/cu_cp/e1ap_cu_cp.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/executors/task_executor.h" #include @@ -50,10 +50,12 @@ class cu_up_processor_impl : public cu_up_processor_interface void handle_cu_up_e1_setup_request(const cu_up_e1_setup_request& msg) override; // getter functions - cu_up_index_t get_cu_up_index() override { return context.cu_up_index; }; - cu_up_processor_context& get_context() override { return context; }; - e1ap_message_handler& get_e1ap_message_handler() override { return *e1ap; }; - e1ap_bearer_context_manager& get_e1ap_bearer_context_manager() override { return *e1ap; } + cu_up_index_t get_cu_up_index() override { return context.cu_up_index; }; + cu_up_processor_context& get_context() override { return context; }; + e1ap_message_handler& get_e1ap_message_handler() override { return *e1ap; }; + e1ap_bearer_context_manager& get_e1ap_bearer_context_manager() override { return *e1ap; } + e1ap_bearer_context_removal_handler& get_e1ap_bearer_context_removal_handler() override { return *e1ap; } + e1ap_statistics_handler& get_e1ap_statistics_handler() override { return *e1ap; } void update_ue_index(ue_index_t ue_index, ue_index_t old_ue_index) override; diff --git a/lib/cu_cp/cu_up_processor/cu_up_processor_repository.cpp b/lib/cu_cp/cu_up_processor/cu_up_processor_repository.cpp index 5cbc8c1745..d3c193fc6e 100644 --- a/lib/cu_cp/cu_up_processor/cu_up_processor_repository.cpp +++ b/lib/cu_cp/cu_up_processor/cu_up_processor_repository.cpp @@ -72,7 +72,7 @@ cu_up_processor_repository::handle_new_cu_up_connection(std::unique_ptrget_e1ap_bearer_context_manager()); + cfg.e1ap_ev_notifier.on_e1ap_created(cu_up_ctxt.cu_up_processor->get_e1ap_bearer_context_manager(), + cu_up_ctxt.cu_up_processor->get_e1ap_bearer_context_removal_handler(), + cu_up_ctxt.cu_up_processor->get_e1ap_statistics_handler()); return cu_up_index; } @@ -146,7 +148,7 @@ void cu_up_processor_repository::remove_cu_up(cu_up_index_t cu_up_index) CORO_BEGIN(ctx); auto du_it = cu_up_db.find(cu_up_index); if (du_it == cu_up_db.end()) { - logger.error("Remove CU-UP called for inexistent cu_up_index={}", cu_up_index); + logger.warning("Remove CU-UP called for inexistent cu_up_index={}", cu_up_index); CORO_EARLY_RETURN(); } @@ -190,6 +192,12 @@ e1ap_bearer_context_manager& cu_up_processor_repository::cu_up_context::get_e1ap return cu_up_processor->get_e1ap_bearer_context_manager(); } +e1ap_bearer_context_removal_handler& +cu_up_processor_repository::cu_up_context::get_e1ap_bearer_context_removal_handler() +{ + return cu_up_processor->get_e1ap_bearer_context_removal_handler(); +} + void cu_up_processor_repository::cu_up_context::update_ue_index(ue_index_t ue_index, ue_index_t old_ue_index) { return cu_up_processor->update_ue_index(ue_index, old_ue_index); diff --git a/lib/cu_cp/cu_up_processor/cu_up_processor_repository.h b/lib/cu_cp/cu_up_processor/cu_up_processor_repository.h index 164755832f..d50de536d4 100644 --- a/lib/cu_cp/cu_up_processor/cu_up_processor_repository.h +++ b/lib/cu_cp/cu_up_processor/cu_up_processor_repository.h @@ -61,9 +61,10 @@ class cu_up_processor_repository : public cu_up_repository /// Notifier used by the CU-CP to push E1AP Tx messages to the respective CU-UP. std::unique_ptr e1ap_tx_pdu_notifier; - e1ap_message_handler& get_e1ap_message_handler() override; - e1ap_bearer_context_manager& get_e1ap_bearer_context_manager() override; - void update_ue_index(ue_index_t ue_index, ue_index_t old_ue_index) override; + e1ap_message_handler& get_e1ap_message_handler() override; + e1ap_bearer_context_manager& get_e1ap_bearer_context_manager() override; + e1ap_bearer_context_removal_handler& get_e1ap_bearer_context_removal_handler() override; + void update_ue_index(ue_index_t ue_index, ue_index_t old_ue_index) override; }; /// \brief Find a CU-UP object. diff --git a/lib/cu_cp/du_processor/du_processor_factory.cpp b/lib/cu_cp/du_processor/du_processor_factory.cpp index 2ab23c9092..b0d1ffee28 100644 --- a/lib/cu_cp/du_processor/du_processor_factory.cpp +++ b/lib/cu_cp/du_processor/du_processor_factory.cpp @@ -35,6 +35,7 @@ srsran::srs_cu_cp::create_du_processor(const du_processor_config_t& du_pr f1ap_message_notifier& f1ap_notifier_, du_processor_e1ap_control_notifier& e1ap_ctrl_notifier_, du_processor_ngap_control_notifier& ngap_ctrl_notifier_, + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier_, rrc_ue_nas_notifier& rrc_ue_nas_pdu_notifier_, rrc_ue_control_notifier& rrc_ue_ngap_ctrl_notifier_, rrc_ue_reestablishment_notifier& rrc_ue_cu_cp_notifier_, @@ -49,6 +50,7 @@ srsran::srs_cu_cp::create_du_processor(const du_processor_config_t& du_pr f1ap_notifier_, e1ap_ctrl_notifier_, ngap_ctrl_notifier_, + f1ap_cu_cp_notifier_, rrc_ue_nas_pdu_notifier_, rrc_ue_ngap_ctrl_notifier_, rrc_ue_cu_cp_notifier_, diff --git a/lib/cu_cp/du_processor/du_processor_factory.h b/lib/cu_cp/du_processor/du_processor_factory.h index 6f38b6d625..e275706c92 100644 --- a/lib/cu_cp/du_processor/du_processor_factory.h +++ b/lib/cu_cp/du_processor/du_processor_factory.h @@ -40,6 +40,7 @@ std::unique_ptr create_du_processor(const du_processor_c f1ap_message_notifier& f1ap_notifier_, du_processor_e1ap_control_notifier& e1ap_ctrl_notifier_, du_processor_ngap_control_notifier& ngap_ctrl_notifier_, + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier_, rrc_ue_nas_notifier& rrc_ue_nas_pdu_notifier_, rrc_ue_control_notifier& rrc_ue_ngap_ctrl_notifier_, rrc_ue_reestablishment_notifier& rrc_ue_cu_cp_notifier_, diff --git a/lib/cu_cp/du_processor/du_processor_impl.cpp b/lib/cu_cp/du_processor/du_processor_impl.cpp index 37727b74fa..76805f04dd 100644 --- a/lib/cu_cp/du_processor/du_processor_impl.cpp +++ b/lib/cu_cp/du_processor/du_processor_impl.cpp @@ -37,6 +37,7 @@ du_processor_impl::du_processor_impl(const du_processor_config_t& du_proc f1ap_message_notifier& f1ap_notifier_, du_processor_e1ap_control_notifier& e1ap_ctrl_notifier_, du_processor_ngap_control_notifier& ngap_ctrl_notifier_, + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier_, rrc_ue_nas_notifier& rrc_ue_nas_pdu_notifier_, rrc_ue_control_notifier& rrc_ue_ngap_ctrl_notifier_, rrc_ue_reestablishment_notifier& rrc_ue_cu_cp_notifier_, @@ -50,6 +51,7 @@ du_processor_impl::du_processor_impl(const du_processor_config_t& du_proc f1ap_notifier(f1ap_notifier_), e1ap_ctrl_notifier(e1ap_ctrl_notifier_), ngap_ctrl_notifier(ngap_ctrl_notifier_), + f1ap_cu_cp_notifier(f1ap_cu_cp_notifier_), rrc_ue_nas_pdu_notifier(rrc_ue_nas_pdu_notifier_), rrc_ue_ngap_ctrl_notifier(rrc_ue_ngap_ctrl_notifier_), rrc_ue_cu_cp_notifier(rrc_ue_cu_cp_notifier_), @@ -60,10 +62,14 @@ du_processor_impl::du_processor_impl(const du_processor_config_t& du_proc context.du_index = cfg.du_index; // create f1ap - f1ap = - create_f1ap(f1ap_notifier, f1ap_ev_notifier, f1ap_du_mgmt_notifier, task_sched.get_timer_manager(), ctrl_exec_); - f1ap_ev_notifier.connect_du_processor(get_du_processor_f1ap_interface()); + f1ap = create_f1ap(f1ap_notifier, + f1ap_ev_notifier, + f1ap_du_mgmt_notifier, + f1ap_cu_cp_notifier, + task_sched.get_timer_manager(), + ctrl_exec_); + f1ap_ev_notifier.connect_du_processor(get_du_processor_f1ap_interface()); f1ap_ue_context_notifier.connect_f1(f1ap->get_f1ap_ue_context_manager()); // create RRC @@ -80,6 +86,12 @@ du_processor_impl::du_processor_impl(const du_processor_config_t& du_proc routine_mng = std::make_unique( e1ap_ctrl_notifier, f1ap_ue_context_notifier, ue_manager, cfg.default_security_indication, logger); + + cu_cp_notifier.on_du_processor_created(context.du_index, + f1ap->get_f1ap_ue_context_removal_handler(), + f1ap->get_f1ap_statistics_handler(), + rrc->get_rrc_ue_removal_handler(), + rrc->get_rrc_du_statistics_handler()); } void du_processor_impl::handle_f1_setup_request(const f1ap_f1_setup_request& request) @@ -88,7 +100,7 @@ void du_processor_impl::handle_f1_setup_request(const f1ap_f1_setup_request& req // Reject request without served cells if (request.gnb_du_served_cells_list.size() == 0) { - logger.error("Not handling F1 setup without served cells"); + logger.warning("Not handling F1 setup without served cells"); send_f1_setup_failure(cause_radio_network_t::unspecified); return; } @@ -112,27 +124,27 @@ void du_processor_impl::handle_f1_setup_request(const f1ap_f1_setup_request& req du_cell.cell_index = get_next_du_cell_index(); if (du_cell.cell_index == du_cell_index_t::invalid) { - logger.error("Not handling F1 setup, maximum number of DU cells reached"); + logger.warning("Not handling F1 setup, maximum number of DU cells reached"); send_f1_setup_failure(cause_radio_network_t::unspecified); return; } du_cell.cgi = served_cell.served_cell_info.nr_cgi; if (not srsran::config_helpers::is_valid(du_cell.cgi)) { - logger.error("Not handling F1 setup, invalid CGI for cell {}", du_cell.cell_index); + logger.warning("Not handling F1 setup, invalid CGI for cell {}", du_cell.cell_index); send_f1_setup_failure(cause_radio_network_t::unspecified); return; } du_cell.pci = served_cell.served_cell_info.nr_pci; if (not srsran::config_helpers::is_valid(du_cell.pci)) { - logger.error("Not handling F1 setup, invalid PCI for cell {}", du_cell.pci); + logger.warning("Not handling F1 setup, invalid PCI for cell {}", du_cell.pci); send_f1_setup_failure(cause_radio_network_t::unspecified); return; } if (not served_cell.served_cell_info.five_gs_tac.has_value()) { - logger.error("Not handling F1 setup, missing TAC for cell {}", du_cell.cell_index); + logger.warning("Not handling F1 setup, missing TAC for cell {}", du_cell.cell_index); send_f1_setup_failure(cause_radio_network_t::unspecified); return; } else { @@ -140,7 +152,7 @@ void du_processor_impl::handle_f1_setup_request(const f1ap_f1_setup_request& req } if (not served_cell.gnb_du_sys_info.has_value()) { - logger.error("Not handling served cells without system information"); + logger.warning("Not handling served cells without system information"); send_f1_setup_failure(cause_radio_network_t::unspecified); return; } else { @@ -234,7 +246,7 @@ du_cell_index_t du_processor_impl::get_next_du_cell_index() return cell_idx; } } - logger.error("No DU cell index available"); + logger.warning("No DU cell index available"); return du_cell_index_t::invalid; } @@ -270,7 +282,7 @@ bool du_processor_impl::create_rrc_ue(du_ue& ue, rrc_ue_create_msg.is_inter_cu_handover = is_inter_cu_handover; auto* rrc_ue = rrc_du_adapter.on_ue_creation_request(ue.get_up_resource_manager(), std::move(rrc_ue_create_msg)); if (rrc_ue == nullptr) { - logger.error("Could not create RRC UE"); + logger.warning("Could not create RRC UE"); return false; } @@ -281,28 +293,39 @@ bool du_processor_impl::create_rrc_ue(du_ue& ue, ue.set_rrc_ue_notifier(rrc_ue_adapters.at(ue.get_ue_index())); ue.set_rrc_ue_srb_notifier(rrc_ue_adapters.at(ue.get_ue_index())); - // Notifiy CU-CP about the creation of the RRC UE - cu_cp_notifier.on_rrc_ue_created(context.du_index, ue.get_ue_index(), *rrc_ue); + // Notify CU-CP about the creation of the RRC UE + cu_cp_notifier.on_rrc_ue_created(ue.get_ue_index(), *rrc_ue); return true; } ue_creation_complete_message du_processor_impl::handle_ue_creation_request(const cu_cp_ue_creation_message& msg) { + srsran_assert(msg.ue_index != ue_index_t::invalid, "Invalid UE index", msg.ue_index); + srsran_assert(srsran::config_helpers::is_valid(msg.cgi), "ue={}: Invalid CGI", msg.ue_index); + srsran_assert(msg.c_rnti != INVALID_RNTI, "ue={}: Invalid C-RNTI", msg.c_rnti); + ue_creation_complete_message ue_creation_complete_msg = {}; ue_creation_complete_msg.ue_index = ue_index_t::invalid; // Check that creation message is valid du_cell_index_t pcell_index = find_cell(msg.cgi.nci); if (pcell_index == du_cell_index_t::invalid) { - logger.error("ue={}: Could not find cell with cell_id={}", msg.ue_index, msg.cgi.nci); + logger.warning("ue={}: Could not find cell with cell_id={}", msg.ue_index, msg.cgi.nci); + return ue_creation_complete_msg; + } + + // Check that the PCI is valid + pci_t pci = cell_db.at(pcell_index).pci; + if (pci == INVALID_PCI) { + logger.warning("ue={} pci={}: Invalid PCI", msg.ue_index, pci); return ue_creation_complete_msg; } // Create new UE context - du_ue* ue = ue_manager.add_ue(msg.ue_index, cell_db.at(pcell_index).pci, msg.c_rnti); + du_ue* ue = ue_manager.add_ue(msg.ue_index, pci, msg.c_rnti); if (ue == nullptr) { - logger.error("ue={}: Could not create UE context", msg.ue_index); + logger.warning("ue={}: Could not create UE context", msg.ue_index); return ue_creation_complete_msg; } @@ -312,7 +335,7 @@ ue_creation_complete_message du_processor_impl::handle_ue_creation_request(const // Create RRC UE only if all RRC-related values are available already. if (!msg.du_to_cu_rrc_container.empty()) { if (create_rrc_ue(*ue, msg.c_rnti, msg.cgi, msg.du_to_cu_rrc_container.copy(), msg.is_inter_cu_handover) == false) { - logger.error("ue={}: Could not create RRC UE object", msg.ue_index); + logger.warning("ue={}: Could not create RRC UE object", msg.ue_index); return ue_creation_complete_msg; } @@ -323,7 +346,7 @@ ue_creation_complete_message du_processor_impl::handle_ue_creation_request(const ue_creation_complete_msg.f1ap_rrc_notifier = &f1ap_rrc_ue_adapters.at(msg.ue_index); } - logger.info("ue={} c-rnti={}: UE Created", ue->get_ue_index(), msg.c_rnti); + logger.info("ue={} c-rnti={}: UE created", ue->get_ue_index(), msg.c_rnti); ue_creation_complete_msg.ue_index = ue->get_ue_index(); @@ -341,7 +364,7 @@ ue_update_complete_message du_processor_impl::handle_ue_update_request(const ue_ if (rrc_ue_adapters.find(ue->get_ue_index()) != rrc_ue_adapters.end()) { if (!msg.cell_group_cfg.empty() && msg.c_rnti != INVALID_RNTI) { if (!create_rrc_ue(*ue, msg.c_rnti, msg.cgi, msg.cell_group_cfg.copy())) { - logger.error("ue={}: Could not create RRC UE object", msg.ue_index); + logger.warning("ue={}: Could not create RRC UE object", msg.ue_index); return ue_update_complete_msg; } @@ -359,25 +382,37 @@ ue_update_complete_message du_processor_impl::handle_ue_update_request(const ue_ void du_processor_impl::handle_du_initiated_ue_context_release_request(const f1ap_ue_context_release_request& request) { + srsran_assert(request.ue_index != ue_index_t::invalid, "Invalid UE index", request.ue_index); + du_ue* ue = ue_manager.find_du_ue(request.ue_index); if (ue == nullptr) { logger.warning("ue={}: Dropping DU initiated UE context release request. UE does not exist", request.ue_index); return; } - cu_cp_ue_context_release_request ue_context_release_request; - ue_context_release_request.ue_index = request.ue_index; - ue_context_release_request.cause = request.cause; + logger.debug("ue={}: Handling DU initiated UE context release request", request.ue_index); + + // Schedule on UE task scheduler + task_sched.schedule_async_task( + request.ue_index, launch_async([this, request, ue](coro_context>& ctx) { + CORO_BEGIN(ctx); - // Add PDU Session IDs - auto& up_resource_manager = ue->get_up_resource_manager(); - ue_context_release_request.pdu_session_res_list_cxt_rel_req = up_resource_manager.get_pdu_sessions(); + cu_cp_ue_context_release_request ue_context_release_request; + ue_context_release_request.ue_index = request.ue_index; + ue_context_release_request.cause = request.cause; - ngap_ctrl_notifier.on_ue_context_release_request(ue_context_release_request); + // Add PDU Session IDs + auto& up_resource_manager = ue->get_up_resource_manager(); + ue_context_release_request.pdu_session_res_list_cxt_rel_req = up_resource_manager.get_pdu_sessions(); + + ngap_ctrl_notifier.on_ue_context_release_request(ue_context_release_request); + + CORO_RETURN(); + })); } async_task -du_processor_impl::handle_ue_context_release_command(const rrc_ue_context_release_command& cmd) +du_processor_impl::handle_ue_context_release_command(const cu_cp_ue_context_release_command& cmd) { du_ue* ue = ue_manager.find_du_ue(cmd.ue_index); if (ue == nullptr) { @@ -388,7 +423,7 @@ du_processor_impl::handle_ue_context_release_command(const rrc_ue_context_releas }); } - return routine_mng->start_ue_context_release_routine(cmd, get_du_processor_ue_handler(), task_sched); + return routine_mng->start_ue_context_release_routine(cmd, cu_cp_notifier); } async_task @@ -408,7 +443,7 @@ du_processor_impl::handle_ue_context_release_command(const cu_cp_ngap_ue_context rrc_ue_release_context release_context = ue->get_rrc_ue_notifier().get_rrc_ue_release_context(); // Create release command from NGAP UE context release command - rrc_ue_context_release_command release_command; + cu_cp_ue_context_release_command release_command; release_command.ue_index = cmd.ue_index; release_command.cause = cmd.cause; release_command.rrc_release_pdu = release_context.rrc_release_pdu.copy(); @@ -557,6 +592,8 @@ void du_processor_impl::handle_inactivity_notification(const cu_cp_inactivity_no auto& up_resource_manager = ue->get_up_resource_manager(); req.pdu_session_res_list_cxt_rel_req = up_resource_manager.get_pdu_sessions(); + logger.debug("ue={}: Requesting UE context release due to inactivity", req.ue_index); + ngap_ctrl_notifier.on_ue_context_release_request(req); } else { logger.debug("Inactivity notification level not supported"); @@ -583,22 +620,6 @@ bool du_processor_impl::has_cell(nr_cell_global_id_t cgi) return false; } -async_task du_processor_impl::remove_ue(ue_index_t ue_index) -{ - return launch_async([this, ue_index](coro_context>& ctx) { - CORO_BEGIN(ctx); - - // Remove UE from RRC - rrc_du_adapter.on_ue_context_release_command(ue_index); - - // Remove UE from UE database - logger.info("ue={}: Removing DU UE", ue_index); - ue_manager.remove_du_ue(ue_index); - - CORO_RETURN(); - }); -} - optional du_processor_impl::get_cgi(pci_t pci) { optional cgi; @@ -617,11 +638,8 @@ async_task du_processor_impl::handle_inter_du_ du_ue* ue = ue_manager.find_du_ue(msg.source_ue_index); srsran_assert(ue != nullptr, "ue={}: Could not find DU UE", msg.source_ue_index); - return routine_mng->start_inter_du_handover_routine(msg, - get_du_processor_ue_handler(), - target_du_f1ap_ue_ctxt_notif_, - ue->get_rrc_ue_notifier(), - ue->get_up_resource_manager()); + return routine_mng->start_inter_du_handover_routine( + msg, cu_cp_notifier, target_du_f1ap_ue_ctxt_notif_, ue->get_rrc_ue_notifier(), ue->get_up_resource_manager()); } async_task @@ -633,5 +651,5 @@ du_processor_impl::handle_inter_ngran_node_n2_handover_request(const cu_cp_inter async_task du_processor_impl::handle_ngap_handover_request(const ngap_handover_request& request) { - return routine_mng->start_inter_cu_handover_target_routine(request, get_du_processor_ue_handler()); + return routine_mng->start_inter_cu_handover_target_routine(request, cu_cp_notifier); } diff --git a/lib/cu_cp/du_processor/du_processor_impl.h b/lib/cu_cp/du_processor/du_processor_impl.h index d7648e97d3..396844fe03 100644 --- a/lib/cu_cp/du_processor/du_processor_impl.h +++ b/lib/cu_cp/du_processor/du_processor_impl.h @@ -47,6 +47,7 @@ class du_processor_impl : public du_processor_interface f1ap_message_notifier& f1ap_notifier_, du_processor_e1ap_control_notifier& e1ap_ctrl_notifier_, du_processor_ngap_control_notifier& ngap_ctrl_notifier_, + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier_, rrc_ue_nas_notifier& rrc_ue_nas_pdu_notifier_, rrc_ue_control_notifier& rrc_ue_ngap_ctrl_notifier_, rrc_ue_reestablishment_notifier& rrc_ue_cu_cp_notifier_, @@ -64,7 +65,7 @@ class du_processor_impl : public du_processor_interface f1ap_statistics_handler& get_f1ap_statistics_handler() override { return *f1ap; } rrc_amf_connection_handler& get_rrc_amf_connection_handler() override { return *rrc; }; - size_t get_nof_ues() override { return ue_manager.get_nof_du_ues(context.du_index); }; + size_t get_nof_ues() const override { return ue_manager.get_nof_du_ues(context.du_index); }; // du_processor_f1ap_interface void handle_f1_setup_request(const f1ap_f1_setup_request& request) override; @@ -75,7 +76,7 @@ class du_processor_impl : public du_processor_interface // du_processor_rrc_ue_interface async_task - handle_ue_context_release_command(const rrc_ue_context_release_command& cmd) override; + handle_ue_context_release_command(const cu_cp_ue_context_release_command& cmd) override; async_task handle_rrc_reestablishment_context_modification_required(ue_index_t ue_index) override; // du_processor_ngap_interface @@ -104,9 +105,6 @@ class du_processor_impl : public du_processor_interface // du_processor inactivity handler void handle_inactivity_notification(const cu_cp_inactivity_notification& msg) override; - // du_processor ue handler - async_task remove_ue(ue_index_t ue_index) override; - // du_processor_cell_info_interface bool has_cell(pci_t pci) override; bool has_cell(nr_cell_global_id_t cgi) override; @@ -127,7 +125,6 @@ class du_processor_impl : public du_processor_interface du_processor_paging_handler& get_du_processor_paging_handler() override { return *this; } du_processor_inactivity_handler& get_du_processor_inactivity_handler() override { return *this; } du_processor_statistics_handler& get_du_processor_statistics_handler() override { return *this; } - du_processor_ue_handler& get_du_processor_ue_handler() override { return *this; } du_processor_mobility_handler& get_du_processor_mobility_handler() override { return *this; } du_processor_f1ap_ue_context_notifier& get_du_processor_f1ap_ue_context_notifier() override { @@ -173,6 +170,7 @@ class du_processor_impl : public du_processor_interface f1ap_message_notifier& f1ap_notifier; du_processor_e1ap_control_notifier& e1ap_ctrl_notifier; du_processor_ngap_control_notifier& ngap_ctrl_notifier; + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier; rrc_ue_nas_notifier& rrc_ue_nas_pdu_notifier; rrc_ue_control_notifier& rrc_ue_ngap_ctrl_notifier; rrc_ue_reestablishment_notifier& rrc_ue_cu_cp_notifier; diff --git a/lib/cu_cp/du_processor/du_processor_repository.cpp b/lib/cu_cp/du_processor/du_processor_repository.cpp index 2256fcfa5c..7a4ef60e93 100644 --- a/lib/cu_cp/du_processor/du_processor_repository.cpp +++ b/lib/cu_cp/du_processor/du_processor_repository.cpp @@ -62,7 +62,7 @@ class f1ap_rx_pdu_notifier final : public f1ap_message_notifier du_processor_repository::du_processor_repository(du_repository_config cfg_) : cfg(cfg_), logger(cfg.logger), du_task_sched(*cfg.cu_cp.timers, *cfg.cu_cp.cu_cp_executor, cfg.logger) { - f1ap_ev_notifier.connect_cu_cp(*this); + f1ap_ev_notifier.connect_du_repository(*this); } std::unique_ptr @@ -70,7 +70,7 @@ du_processor_repository::handle_new_du_connection(std::unique_ptrsecond; - du_ctxt.du_to_cu_cp_notifier.connect_cu_cp(cfg.cu_cp_du_handler, du_ctxt.ngap_du_processor_notifier); + du_ctxt.du_to_cu_cp_notifier.connect_cu_cp( + cfg.cu_cp_du_handler, cfg.ue_removal_handler, du_ctxt.ngap_du_processor_notifier); du_ctxt.f1ap_tx_pdu_notifier = std::move(f1ap_tx_pdu_notifier); // TODO: use real config @@ -115,6 +116,7 @@ du_index_t du_processor_repository::add_du(std::unique_ptr du_processor_repository::request_ue_removal(du_index_t du_index, ue_index_t ue_index) -{ - return du_db.at(du_index).du_processor->get_du_processor_ue_handler().remove_ue(ue_index); -} - void du_processor_repository::handle_inactivity_notification(du_index_t du_index, const cu_cp_inactivity_notification& msg) { diff --git a/lib/cu_cp/du_processor/du_processor_repository.h b/lib/cu_cp/du_processor/du_processor_repository.h index 014eed1b7a..f8cc1f0c98 100644 --- a/lib/cu_cp/du_processor/du_processor_repository.h +++ b/lib/cu_cp/du_processor/du_processor_repository.h @@ -42,8 +42,10 @@ struct cu_cp_configuration; struct du_repository_config { const cu_cp_configuration& cu_cp; cu_cp_du_event_handler& cu_cp_du_handler; + cu_cp_ue_removal_handler& ue_removal_handler; du_processor_e1ap_control_notifier& e1ap_ctrl_notifier; du_processor_ngap_control_notifier& ngap_ctrl_notifier; + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier; rrc_ue_nas_notifier& ue_nas_pdu_notifier; rrc_ue_control_notifier& ue_ngap_ctrl_notifier; rrc_ue_reestablishment_notifier& rrc_ue_cu_cp_notifier; @@ -78,8 +80,6 @@ class du_processor_repository : public du_repository, public cu_cp_du_repository void handle_amf_connection(); void handle_amf_connection_drop(); - async_task request_ue_removal(du_index_t du_index, ue_index_t ue_index); - void handle_inactivity_notification(du_index_t du_index, const cu_cp_inactivity_notification& msg); private: @@ -122,8 +122,8 @@ class du_processor_repository : public du_repository, public cu_cp_du_repository du_repository_config cfg; srslog::basic_logger& logger; - // F1AP to CU-CP adapter. - f1ap_cu_cp_adapter f1ap_ev_notifier; + // F1AP to DU repository adapter. + f1ap_du_repository_adapter f1ap_ev_notifier; du_task_scheduler du_task_sched; diff --git a/lib/cu_cp/routine_managers/cu_cp_routine_manager.cpp b/lib/cu_cp/routine_managers/cu_cp_routine_manager.cpp index 0b1f40e292..43e3b2e039 100644 --- a/lib/cu_cp/routine_managers/cu_cp_routine_manager.cpp +++ b/lib/cu_cp/routine_managers/cu_cp_routine_manager.cpp @@ -22,13 +22,19 @@ #include "cu_cp_routine_manager.h" #include "../routines/initial_cu_cp_setup_routine.h" +#include "../routines/ue_removal_routine.h" +#include "srsran/support/async/coroutine.h" using namespace srsran; using namespace srs_cu_cp; cu_cp_routine_manager::cu_cp_routine_manager(cu_cp_ngap_control_notifier& ngap_ctrl_notifier_, - ngap_cu_cp_connection_notifier& cu_cp_ngap_ev_notifier_) : - ngap_ctrl_notifier(ngap_ctrl_notifier_), cu_cp_ngap_ev_notifier(cu_cp_ngap_ev_notifier_), main_ctrl_loop(128) + ngap_cu_cp_connection_notifier& cu_cp_ngap_ev_notifier_, + ue_task_scheduler& ue_task_sched_) : + ngap_ctrl_notifier(ngap_ctrl_notifier_), + cu_cp_ngap_ev_notifier(cu_cp_ngap_ev_notifier_), + ue_task_sched(ue_task_sched_), + main_ctrl_loop(128) { } @@ -37,3 +43,17 @@ void cu_cp_routine_manager::start_initial_cu_cp_setup_routine(const ngap_configu main_ctrl_loop.schedule( launch_async(ngap_cfg, ngap_ctrl_notifier, cu_cp_ngap_ev_notifier)); } + +void cu_cp_routine_manager::start_ue_removal_routine(ue_index_t ue_index, + cu_cp_rrc_ue_removal_notifier& rrc_du_notifier, + cu_cp_e1ap_ue_removal_notifier& e1ap_notifier, + cu_cp_f1ap_ue_removal_notifier& f1ap_notifier, + cu_cp_ngap_control_notifier& ngap_notifier, + ue_manager& ue_mng, + srslog::basic_logger& logger) +{ + ue_task_sched.handle_ue_async_task( + ue_index, + launch_async( + ue_index, rrc_du_notifier, e1ap_notifier, f1ap_notifier, ngap_notifier, ue_mng, ue_task_sched, logger)); +} diff --git a/lib/cu_cp/routine_managers/cu_cp_routine_manager.h b/lib/cu_cp/routine_managers/cu_cp_routine_manager.h index 1098740d7a..e57c5c6775 100644 --- a/lib/cu_cp/routine_managers/cu_cp_routine_manager.h +++ b/lib/cu_cp/routine_managers/cu_cp_routine_manager.h @@ -22,10 +22,14 @@ #pragma once +#include "../adapters/cu_cp_adapters.h" #include "../cu_cp_impl_interface.h" +#include "../task_schedulers/ue_task_scheduler.h" +#include "../ue_manager_impl.h" #include "srsran/cu_cp/cu_cp_configuration.h" +#include "srsran/cu_cp/cu_cp_types.h" #include "srsran/ngap/ngap.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include namespace srsran { @@ -36,17 +40,27 @@ class cu_cp_routine_manager { public: explicit cu_cp_routine_manager(cu_cp_ngap_control_notifier& ngap_ctrl_notifier_, - ngap_cu_cp_connection_notifier& cu_cp_ngap_ev_notifier_); + ngap_cu_cp_connection_notifier& cu_cp_ngap_ev_notifier_, + ue_task_scheduler& ue_task_sched_); ~cu_cp_routine_manager() = default; void start_initial_cu_cp_setup_routine(const ngap_configuration& ngap_cfg); + void start_ue_removal_routine(ue_index_t ue_index, + cu_cp_rrc_ue_removal_notifier& rrc_du_notifier, + cu_cp_e1ap_ue_removal_notifier& e1ap_notifier, + cu_cp_f1ap_ue_removal_notifier& f1ap_notifier, + cu_cp_ngap_control_notifier& ngap_notifier, + ue_manager& ue_mng, + srslog::basic_logger& logger); + private: cu_cp_ngap_control_notifier& ngap_ctrl_notifier; ngap_cu_cp_connection_notifier& cu_cp_ngap_ev_notifier; + ue_task_scheduler& ue_task_sched; // cu-cp task event loop - async_task_sequencer main_ctrl_loop; + fifo_async_task_scheduler main_ctrl_loop; }; } // namespace srs_cu_cp diff --git a/lib/cu_cp/routine_managers/du_processor_routine_manager.cpp b/lib/cu_cp/routine_managers/du_processor_routine_manager.cpp index 84f1163bdd..30b0b777e9 100644 --- a/lib/cu_cp/routine_managers/du_processor_routine_manager.cpp +++ b/lib/cu_cp/routine_managers/du_processor_routine_manager.cpp @@ -93,12 +93,11 @@ du_processor_routine_manager::start_pdu_session_resource_release_routine( } async_task -du_processor_routine_manager::start_ue_context_release_routine(const rrc_ue_context_release_command& command, - du_processor_ue_handler& du_processor_notifier, - du_processor_ue_task_scheduler& task_scheduler) +du_processor_routine_manager::start_ue_context_release_routine(const cu_cp_ue_context_release_command& command, + du_processor_cu_cp_notifier& cu_cp_notifier) { return launch_async( - command, e1ap_ctrl_notifier, f1ap_ue_ctxt_notifier, du_processor_notifier, ue_manager, task_scheduler, logger); + command, e1ap_ctrl_notifier, f1ap_ue_ctxt_notifier, cu_cp_notifier, ue_manager, logger); } async_task du_processor_routine_manager::start_reestablishment_context_modification_routine( @@ -112,13 +111,13 @@ async_task du_processor_routine_manager::start_reestablishment_context_mod async_task du_processor_routine_manager::start_inter_du_handover_routine( const cu_cp_inter_du_handover_request& command, - du_processor_ue_handler& du_proc_ue_handler, + du_processor_cu_cp_notifier& cu_cp_notifier, du_processor_f1ap_ue_context_notifier& target_du_f1ap_ue_ctxt_notifier, du_processor_rrc_ue_control_message_notifier& rrc_ue_ctrl_notifier, up_resource_manager& ue_up_resource_manager) { return launch_async(command, - du_proc_ue_handler, + cu_cp_notifier, f1ap_ue_ctxt_notifier, target_du_f1ap_ue_ctxt_notifier, e1ap_ctrl_notifier, @@ -138,12 +137,12 @@ du_processor_routine_manager::start_inter_ngran_node_n2_handover_routine( async_task du_processor_routine_manager::start_inter_cu_handover_target_routine(const ngap_handover_request& request_, - du_processor_ue_handler& du_proc_ue_handler) + du_processor_cu_cp_notifier& cu_cp_notifier) { return launch_async(request_, f1ap_ue_ctxt_notifier, e1ap_ctrl_notifier, - du_proc_ue_handler, + cu_cp_notifier, ue_manager, default_security_indication, logger); diff --git a/lib/cu_cp/routine_managers/du_processor_routine_manager.h b/lib/cu_cp/routine_managers/du_processor_routine_manager.h index 2d7fccc40a..8349d694a7 100644 --- a/lib/cu_cp/routine_managers/du_processor_routine_manager.h +++ b/lib/cu_cp/routine_managers/du_processor_routine_manager.h @@ -58,9 +58,8 @@ class du_processor_routine_manager up_resource_manager& rrc_ue_up_resource_manager); async_task - start_ue_context_release_routine(const rrc_ue_context_release_command& command, - du_processor_ue_handler& du_processor_notifier, - du_processor_ue_task_scheduler& task_scheduler); + start_ue_context_release_routine(const cu_cp_ue_context_release_command& command, + du_processor_cu_cp_notifier& cu_cp_notifier); async_task start_reestablishment_context_modification_routine(ue_index_t ue_index, @@ -69,7 +68,7 @@ class du_processor_routine_manager async_task start_inter_du_handover_routine(const cu_cp_inter_du_handover_request& request, - du_processor_ue_handler& du_proc_ue_handler, + du_processor_cu_cp_notifier& cu_cp_notifier, du_processor_f1ap_ue_context_notifier& target_du_f1ap_ue_ctxt_notifier, du_processor_rrc_ue_control_message_notifier& rrc_ue_ctrl_notifier, up_resource_manager& ue_up_resource_manager); @@ -80,7 +79,7 @@ class du_processor_routine_manager async_task start_inter_cu_handover_target_routine(const ngap_handover_request& request, - du_processor_ue_handler& du_proc_ue_handler); + du_processor_cu_cp_notifier& cu_cp_notifier); private: du_processor_e1ap_control_notifier& e1ap_ctrl_notifier; diff --git a/lib/cu_cp/routines/mobility/inter_cu_handover_target_routine.cpp b/lib/cu_cp/routines/mobility/inter_cu_handover_target_routine.cpp index 621db980fd..715c1f7d04 100644 --- a/lib/cu_cp/routines/mobility/inter_cu_handover_target_routine.cpp +++ b/lib/cu_cp/routines/mobility/inter_cu_handover_target_routine.cpp @@ -48,14 +48,14 @@ inter_cu_handover_target_routine::inter_cu_handover_target_routine( const ngap_handover_request& request_, du_processor_f1ap_ue_context_notifier& f1ap_ue_ctxt_notif_, du_processor_e1ap_control_notifier& e1ap_ctrl_notif_, - du_processor_ue_handler& du_proc_ue_handler_, + du_processor_cu_cp_notifier& cu_cp_notifier_, du_processor_ue_manager& ue_manager_, const security_indication_t& default_security_indication_, srslog::basic_logger& logger_) : request(request_), f1ap_ue_ctxt_notifier(f1ap_ue_ctxt_notif_), e1ap_ctrl_notifier(e1ap_ctrl_notif_), - du_proc_ue_handler(du_proc_ue_handler_), + cu_cp_notifier(cu_cp_notifier_), ue_manager(ue_manager_), logger(logger_), default_security_indication(default_security_indication_) @@ -125,7 +125,7 @@ void inter_cu_handover_target_routine::operator()( if (!handle_procedure_response( bearer_context_modification_request, ue_context_setup_response, next_config, logger)) { logger.error("ue={}: \"{}\" failed to setup UE context at DU.", request.ue_index, name()); - du_proc_ue_handler.remove_ue(ue_context_setup_response.ue_index); + cu_cp_notifier.on_ue_removal_required(ue_context_setup_response.ue_index); CORO_EARLY_RETURN(generate_handover_resource_allocation_response(false)); } } diff --git a/lib/cu_cp/routines/mobility/inter_cu_handover_target_routine.h b/lib/cu_cp/routines/mobility/inter_cu_handover_target_routine.h index 5ab55631b7..dd0a108b46 100644 --- a/lib/cu_cp/routines/mobility/inter_cu_handover_target_routine.h +++ b/lib/cu_cp/routines/mobility/inter_cu_handover_target_routine.h @@ -35,7 +35,7 @@ class inter_cu_handover_target_routine inter_cu_handover_target_routine(const ngap_handover_request& request_, du_processor_f1ap_ue_context_notifier& f1ap_ue_ctxt_notif_, du_processor_e1ap_control_notifier& e1ap_ctrl_notif_, - du_processor_ue_handler& du_proc_ue_handler_, + du_processor_cu_cp_notifier& cu_cp_notifier_, du_processor_ue_manager& ue_manager_, const security_indication_t& default_security_indication_, srslog::basic_logger& logger_); @@ -54,7 +54,7 @@ class inter_cu_handover_target_routine du_processor_f1ap_ue_context_notifier& f1ap_ue_ctxt_notifier; // to trigger UE context creation du_processor_e1ap_control_notifier& e1ap_ctrl_notifier; // to trigger bearer context modification at CU-UP - du_processor_ue_handler& du_proc_ue_handler; // to trigger UE removal if the UE Context Setup fails + du_processor_cu_cp_notifier& cu_cp_notifier; // to trigger UE removal if the UE Context Setup fails du_processor_ue_manager& ue_manager; srslog::basic_logger& logger; diff --git a/lib/cu_cp/routines/mobility/inter_du_handover_routine.cpp b/lib/cu_cp/routines/mobility/inter_du_handover_routine.cpp index 5ba3bf5342..c23c4cb1e6 100644 --- a/lib/cu_cp/routines/mobility/inter_du_handover_routine.cpp +++ b/lib/cu_cp/routines/mobility/inter_du_handover_routine.cpp @@ -32,7 +32,7 @@ using namespace asn1::rrc_nr; inter_du_handover_routine::inter_du_handover_routine( const cu_cp_inter_du_handover_request& command_, - du_processor_ue_handler& du_proc_ue_handler_, + du_processor_cu_cp_notifier& cu_cp_notifier_, du_processor_f1ap_ue_context_notifier& source_du_f1ap_ue_ctxt_notif_, du_processor_f1ap_ue_context_notifier& target_du_f1ap_ue_ctxt_notif_, du_processor_e1ap_control_notifier& e1ap_ctrl_notif_, @@ -41,7 +41,7 @@ inter_du_handover_routine::inter_du_handover_routine( up_resource_manager& ue_up_resource_manager_, srslog::basic_logger& logger_) : command(command_), - du_proc_ue_handler(du_proc_ue_handler_), + cu_cp_notifier(cu_cp_notifier_), source_du_f1ap_ue_ctxt_notifier(source_du_f1ap_ue_ctxt_notif_), target_du_f1ap_ue_ctxt_notifier(target_du_f1ap_ue_ctxt_notif_), e1ap_ctrl_notifier(e1ap_ctrl_notif_), @@ -84,7 +84,7 @@ void inter_du_handover_routine::operator()(coro_context>& ctx); static const char* name() { return "UE Context Release Routine"; } private: - const rrc_ue_context_release_command command; + const cu_cp_ue_context_release_command command; du_processor_e1ap_control_notifier& e1ap_ctrl_notifier; // to trigger bearer context setup at CU-UP du_processor_f1ap_ue_context_notifier& f1ap_ue_ctxt_notifier; // to trigger UE context modification at DU - du_processor_ue_handler& du_processor_notifier; // to remove UE from DU processor - du_processor_ue_manager& ue_manager; // to remove UE context from DU processor - du_processor_ue_task_scheduler& task_scheduler; // to remove pending UE tasks + du_processor_cu_cp_notifier& cu_cp_notifier; // to remove UE + du_processor_ue_manager& ue_manager; srslog::basic_logger& logger; // (sub-)routine requests diff --git a/lib/cu_cp/routines/ue_removal_routine.cpp b/lib/cu_cp/routines/ue_removal_routine.cpp new file mode 100644 index 0000000000..7f641555f5 --- /dev/null +++ b/lib/cu_cp/routines/ue_removal_routine.cpp @@ -0,0 +1,74 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "ue_removal_routine.h" + +using namespace srsran; +using namespace srsran::srs_cu_cp; + +ue_removal_routine::ue_removal_routine(ue_index_t ue_index_, + cu_cp_rrc_ue_removal_notifier& rrc_du_notifier_, + cu_cp_e1ap_ue_removal_notifier& e1ap_notifier_, + cu_cp_f1ap_ue_removal_notifier& f1ap_notifier_, + cu_cp_ngap_control_notifier& ngap_notifier_, + ue_manager& ue_mng_, + ue_task_scheduler& task_scheduler_, + srslog::basic_logger& logger_) : + ue_index(ue_index_), + rrc_du_notifier(rrc_du_notifier_), + e1ap_notifier(e1ap_notifier_), + f1ap_notifier(f1ap_notifier_), + ngap_notifier(ngap_notifier_), + ue_mng(ue_mng_), + task_scheduler(task_scheduler_), + logger(logger_) +{ +} + +void ue_removal_routine::operator()(coro_context>& ctx) +{ + CORO_BEGIN(ctx); + + logger.debug("ue={}: \"{}\" initialized.", ue_index, name()); + + // Remove RRC UE + rrc_du_notifier.remove_ue(ue_index); + + // Remove UE from UE manager + ue_mng.remove_ue(ue_index); + + // Remove Bearer Context from E1AP + e1ap_notifier.remove_ue(ue_index); + + // Remove UE Context from F1AP + f1ap_notifier.remove_ue(ue_index); + + // Remove UE Context from NGAP + ngap_notifier.remove_ue(ue_index); + + // Remove pending UE tasks + task_scheduler.clear_pending_tasks(ue_index); + + logger.debug("ue={}: \"{}\" finalized.", ue_index, name()); + + CORO_RETURN(); +} diff --git a/lib/cu_cp/routines/ue_removal_routine.h b/lib/cu_cp/routines/ue_removal_routine.h new file mode 100644 index 0000000000..eb22f68014 --- /dev/null +++ b/lib/cu_cp/routines/ue_removal_routine.h @@ -0,0 +1,63 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "../cu_cp_impl_interface.h" +#include "../task_schedulers/ue_task_scheduler.h" +#include "../ue_manager_impl.h" +#include "srsran/support/async/async_task.h" +#include "srsran/support/async/eager_async_task.h" + +namespace srsran { +namespace srs_cu_cp { + +/// \brief Removes a UE from the CU-CP +class ue_removal_routine +{ +public: + ue_removal_routine(ue_index_t ue_index_, + cu_cp_rrc_ue_removal_notifier& rrc_du_notifier_, + cu_cp_e1ap_ue_removal_notifier& e1ap_notifier_, + cu_cp_f1ap_ue_removal_notifier& f1ap_notifier_, + cu_cp_ngap_control_notifier& ngap_notifier_, + ue_manager& ue_mng_, + ue_task_scheduler& task_scheduler_, + srslog::basic_logger& logger_); + + void operator()(coro_context>& ctx); + + static const char* name() { return "UE Removal Routine"; } + +private: + const ue_index_t ue_index; + cu_cp_rrc_ue_removal_notifier& rrc_du_notifier; // to trigger removal of the UE at the RRC + cu_cp_e1ap_ue_removal_notifier& e1ap_notifier; // to trigger removal of the UE at the E1AP + cu_cp_f1ap_ue_removal_notifier& f1ap_notifier; // to trigger removal of the UE at the F1AP + cu_cp_ngap_control_notifier& ngap_notifier; // to trigger removal of the UE at the NGAP + ue_manager& ue_mng; // to remove UE context from DU processor + ue_task_scheduler& task_scheduler; // to remove pending UE tasks + srslog::basic_logger& logger; +}; + +} // namespace srs_cu_cp +} // namespace srsran diff --git a/lib/cu_cp/task_schedulers/cu_up_task_scheduler.cpp b/lib/cu_cp/task_schedulers/cu_up_task_scheduler.cpp index 0071db80da..e30bda349a 100644 --- a/lib/cu_cp/task_schedulers/cu_up_task_scheduler.cpp +++ b/lib/cu_cp/task_schedulers/cu_up_task_scheduler.cpp @@ -40,7 +40,7 @@ cu_up_task_scheduler::cu_up_task_scheduler(timer_manager& timers_, // UE task scheduler void cu_up_task_scheduler::handle_cu_up_async_task(cu_up_index_t cu_up_index, async_task&& task) { - logger.debug("cu-up={}: Scheduling async task", cu_up_index); + logger.debug("cu-up={} Scheduling async task", cu_up_index); cu_up_ctrl_loop[cu_up_index_to_uint(cu_up_index)].schedule(std::move(task)); } diff --git a/lib/cu_cp/task_schedulers/cu_up_task_scheduler.h b/lib/cu_cp/task_schedulers/cu_up_task_scheduler.h index 18ccbc604f..f0bbbbeb07 100644 --- a/lib/cu_cp/task_schedulers/cu_up_task_scheduler.h +++ b/lib/cu_cp/task_schedulers/cu_up_task_scheduler.h @@ -24,7 +24,7 @@ #include "srsran/adt/slotted_array.h" #include "srsran/cu_cp/cu_cp_types.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/executors/task_executor.h" #include "srsran/support/timers.h" @@ -50,7 +50,7 @@ class cu_up_task_scheduler srslog::basic_logger& logger; // task event loops indexed by cu_up_index - slotted_array cu_up_ctrl_loop; + slotted_array cu_up_ctrl_loop; }; } // namespace srs_cu_cp diff --git a/lib/cu_cp/task_schedulers/du_task_scheduler.h b/lib/cu_cp/task_schedulers/du_task_scheduler.h index dbd614827a..c755fe9342 100644 --- a/lib/cu_cp/task_schedulers/du_task_scheduler.h +++ b/lib/cu_cp/task_schedulers/du_task_scheduler.h @@ -24,7 +24,7 @@ #include "srsran/adt/slotted_array.h" #include "srsran/cu_cp/cu_cp_types.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/executors/task_executor.h" #include "srsran/support/timers.h" @@ -50,7 +50,7 @@ class du_task_scheduler srslog::basic_logger& logger; // task event loops indexed by du_index - slotted_array du_ctrl_loop; + slotted_array du_ctrl_loop; }; } // namespace srs_cu_cp diff --git a/lib/cu_cp/task_schedulers/ue_task_scheduler.cpp b/lib/cu_cp/task_schedulers/ue_task_scheduler.cpp index ec71a95a94..fed1798e05 100644 --- a/lib/cu_cp/task_schedulers/ue_task_scheduler.cpp +++ b/lib/cu_cp/task_schedulers/ue_task_scheduler.cpp @@ -48,6 +48,11 @@ void ue_task_scheduler::handle_ue_async_task(ue_index_t ue_index, async_task ue_task_scheduler::dispatch_and_await_task_completion(ue_index_t ue_index, unique_task task) +{ + return when_completed_on_task_sched(ue_ctrl_loop.at(ue_index), std::move(task)); +} + unique_timer ue_task_scheduler::make_unique_timer() { return timers.create_unique_timer(exec); diff --git a/lib/cu_cp/task_schedulers/ue_task_scheduler.h b/lib/cu_cp/task_schedulers/ue_task_scheduler.h index c12ac7d2be..9587c39120 100644 --- a/lib/cu_cp/task_schedulers/ue_task_scheduler.h +++ b/lib/cu_cp/task_schedulers/ue_task_scheduler.h @@ -24,7 +24,7 @@ #include "srsran/adt/slotted_array.h" #include "srsran/cu_cp/cu_cp_types.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/executors/task_executor.h" #include "srsran/support/timers.h" #include @@ -44,6 +44,8 @@ class ue_task_scheduler void clear_pending_tasks(ue_index_t ue_index); + async_task dispatch_and_await_task_completion(ue_index_t ue_index, unique_task task); + unique_timer make_unique_timer(); timer_manager& get_timer_manager(); @@ -53,7 +55,7 @@ class ue_task_scheduler srslog::basic_logger& logger; // task event loops indexed by ue_index - std::unordered_map ue_ctrl_loop; + std::unordered_map ue_ctrl_loop; }; } // namespace srs_cu_cp diff --git a/lib/cu_cp/ue_manager_impl.cpp b/lib/cu_cp/ue_manager_impl.cpp index 713bfe1908..4f04f5f2a4 100644 --- a/lib/cu_cp/ue_manager_impl.cpp +++ b/lib/cu_cp/ue_manager_impl.cpp @@ -37,17 +37,49 @@ ue_index_t ue_manager::get_ue_index(pci_t pci, rnti_t rnti) if (pci_rnti_to_ue_index.find(std::make_tuple(pci, rnti)) != pci_rnti_to_ue_index.end()) { return pci_rnti_to_ue_index.at(std::make_tuple(pci, rnti)); } - logger.debug("UE index for pci={} and rnti={} not found.", pci, rnti); + logger.debug("UE index for pci={} and rnti={} not found", pci, rnti); return ue_index_t::invalid; } +void ue_manager::remove_ue(ue_index_t ue_index) +{ + if (ue_index == ue_index_t::invalid) { + logger.warning("Can't remove UE with invalid UE index"); + return; + } + + if (ues.find(ue_index) == ues.end()) { + logger.warning("ue={}: Remove UE called for inexistent UE", ue_index); + return; + } + + // remove UE from lookups + pci_t pci = ues.at(ue_index).get_pci(); + if (pci != INVALID_PCI) { + rnti_t c_rnti = ues.at(ue_index).get_c_rnti(); + if (c_rnti != rnti_t::INVALID_RNTI) { + pci_rnti_to_ue_index.erase(std::make_tuple(pci, c_rnti)); + } else { + logger.warning("ue={} RNTI not found", ue_index); + } + } else { + logger.debug("ue={} PCI not found", ue_index); + } + + // Remove CU-CP UE from database + ues.erase(ue_index); + + logger.info("ue={} removed", ue_index); + return; +} + // du_processor_ue_manager ue_index_t ue_manager::allocate_new_ue_index(du_index_t du_index) { ue_index_t new_ue_index = get_next_ue_index(du_index); if (new_ue_index == ue_index_t::invalid) { - logger.error("No free UE index available."); + logger.warning("No free UE index available"); return new_ue_index; } @@ -55,7 +87,7 @@ ue_index_t ue_manager::allocate_new_ue_index(du_index_t du_index) ues.emplace( std::piecewise_construct, std::forward_as_tuple(new_ue_index), std::forward_as_tuple(new_ue_index, up_config)); - logger.debug("ue={} Allocated new UE index.", new_ue_index); + logger.debug("ue={} Allocated new UE index", new_ue_index); return new_ue_index; } @@ -70,33 +102,19 @@ du_ue* ue_manager::find_ue(ue_index_t ue_index) du_ue* ue_manager::add_ue(ue_index_t ue_index, pci_t pci, rnti_t rnti) { - // check if ue_index is valid - if (ue_index == ue_index_t::invalid) { - logger.error("Invalid ue_index={}.", ue_index); - return nullptr; - } + srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); + srsran_assert(pci != INVALID_PCI, "Invalid pci={}", pci); + srsran_assert(rnti != INVALID_RNTI, "Invalid rnti={}", rnti); // check if ue_index is in db if (ues.find(ue_index) == ues.end()) { - logger.error("UE with ue_index={} not found.", ue_index); - return nullptr; - } - - // check if PCI is valid - if (pci == INVALID_PCI) { - logger.error("Invalid pci={}.", pci); - return nullptr; - } - - // check if RNTI is valid - if (rnti == INVALID_RNTI) { - logger.error("Invalid rnti={}.", rnti); + logger.warning("UE with ue_index={} not found", ue_index); return nullptr; } // check if the UE is already present if (get_ue_index(pci, rnti) != ue_index_t::invalid) { - logger.error("UE with pci={} and rnti={} already exists.", pci, rnti); + logger.warning("UE with pci={} and rnti={} already exists", pci, rnti); return nullptr; } @@ -106,55 +124,14 @@ du_ue* ue_manager::add_ue(ue_index_t ue_index, pci_t pci, rnti_t rnti) // Add PCI and RNTI to lookup. pci_rnti_to_ue_index.emplace(std::make_tuple(pci, rnti), ue_index); - ue.du_ue_created = true; - - logger.debug("ue={} updated UE with pci={} and rnti={}.", ue_index, pci, rnti); + logger.debug("ue={} updated UE with pci={} and rnti={}", ue_index, pci, rnti); return &ue; } -void ue_manager::remove_du_ue(ue_index_t ue_index) -{ - if (ue_index == ue_index_t::invalid) { - logger.error("Can't remove UE with invalid UE index."); - return; - } - - logger.debug("ue={} scheduling deletion.", ue_index); - - if (ues.find(ue_index) == ues.end() || !ues.at(ue_index).du_ue_created) { - logger.error("Remove UE called for inexistent UE (ue={}).", ue_index); - return; - } - - // remove UE from lookups - pci_t pci = ues.at(ue_index).get_pci(); - if (pci != INVALID_PCI) { - rnti_t c_rnti = ues.at(ue_index).get_c_rnti(); - if (c_rnti != rnti_t::INVALID_RNTI) { - pci_rnti_to_ue_index.erase(std::make_tuple(pci, c_rnti)); - } else { - logger.error("ue={} RNTI not found.", ue_index); - } - } else { - logger.debug("ue={} PCI not found.", ue_index); - } - - if (!ues.at(ue_index).ngap_ue_created) { - // if NGAP UE was not created or already removed, remove CU-CP UE from database - ues.erase(ue_index); - } else { - // Mark DU UE as removed - ues.at(ue_index).du_ue_created = false; - } - - logger.info("ue={} removed.", ue_index); - return; -} - du_ue* ue_manager::find_du_ue(ue_index_t ue_index) { - if (ues.find(ue_index) != ues.end() && ues.at(ue_index).du_ue_created) { + if (ues.find(ue_index) != ues.end()) { return &ues.at(ue_index); } return nullptr; @@ -167,151 +144,37 @@ ngap_ue* ue_manager::add_ue(ue_index_t ue_index, ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier_, ngap_du_processor_control_notifier& du_processor_ctrl_notifier_) { - // check if ue index is valid - if (ue_index == ue_index_t::invalid) { - logger.error("Can't add UE with invalid UE index."); - return nullptr; - } + srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); // check if the UE is already present - if (ues.find(ue_index) != ues.end() && ues.at(ue_index).ngap_ue_created) { - logger.error("ue={} already exists.", ue_index); + if (ues.find(ue_index) != ues.end() && ues.at(ue_index).ngap_ue_created()) { + logger.warning("ue={} already exists", ue_index); return nullptr; } // UE must be created by DU processor if (ues.find(ue_index) == ues.end()) { - logger.error("UE has not been created."); - return nullptr; - } - - ran_ue_id_t ran_ue_id = get_next_ran_ue_id(); - if (ran_ue_id == ran_ue_id_t::invalid) { - logger.error("No free RAN UE ID available."); + logger.warning("UE has not been created"); return nullptr; } auto& ue = ues.at(ue_index); - ue.set_ran_ue_id(ran_ue_id); - ue.set_rrc_ue_pdu_notifier(rrc_ue_pdu_notifier_); - ue.set_rrc_ue_ctrl_notifier(rrc_ue_ctrl_notifier_); - ue.set_du_processor_ctrl_notifier(du_processor_ctrl_notifier_); - - // Add RAN UE ID to lookup - ran_ue_id_to_ue_index.emplace(ran_ue_id, ue_index); + ue.add_ngap_ue_context(rrc_ue_pdu_notifier_, rrc_ue_ctrl_notifier_, du_processor_ctrl_notifier_); - ue.ngap_ue_created = true; - - logger.debug("ue={} Added NGAP context to UE with ran_ue_id={}.", ue_index, ran_ue_id); + logger.debug("ue={}: Added NGAP UE", ue_index); return &ue; } -void ue_manager::remove_ngap_ue(ue_index_t ue_index) -{ - if (ue_index == ue_index_t::invalid) { - logger.error("Can't remove UE with invalid UE index."); - return; - } - - logger.debug("ue={} Scheduling deletion.", ue_index); - - if (ues.find(ue_index) == ues.end() || !ues.at(ue_index).ngap_ue_created) { - logger.error("Remove UE called for inexistent UE (ue={}).", ue_index); - return; - } - - // Remove UE from lookups - ran_ue_id_t ran_ue_id = ues.at(ue_index).get_ran_ue_id(); - if (ran_ue_id != ran_ue_id_t::invalid) { - ran_ue_id_to_ue_index.erase(ran_ue_id); - } - - amf_ue_id_t amf_ue_id = ues.at(ue_index).get_amf_ue_id(); - if (amf_ue_id != amf_ue_id_t::invalid) { - amf_ue_id_to_ue_index.erase(amf_ue_id); - } - - if (!ues.at(ue_index).du_ue_created) { - // if DU UE was not created or already removed, remove CU-CP UE from database - ues.erase(ue_index); - } else { - // Mark NGAP UE as removed - ues.at(ue_index).ngap_ue_created = false; - } -} - ngap_ue* ue_manager::find_ngap_ue(ue_index_t ue_index) { - if (ues.find(ue_index) != ues.end() && ues.at(ue_index).ngap_ue_created) { + if (ues.find(ue_index) != ues.end() && ues.at(ue_index).ngap_ue_created()) { return &ues.at(ue_index); } return nullptr; } -ue_index_t ue_manager::get_ue_index(ran_ue_id_t ran_ue_id) -{ - if (ran_ue_id_to_ue_index.find(ran_ue_id) == ran_ue_id_to_ue_index.end()) { - logger.info("UE with ran_ue_id={} doesn't exist - dropping PDU.", ran_ue_id); - return ue_index_t::invalid; - } - return ran_ue_id_to_ue_index[ran_ue_id]; -} - -ue_index_t ue_manager::get_ue_index(amf_ue_id_t amf_ue_id) -{ - if (amf_ue_id_to_ue_index.find(amf_ue_id) == amf_ue_id_to_ue_index.end()) { - logger.info("UE with amf_ue_id={} doesn't exist - dropping PDU.", amf_ue_id); - return ue_index_t::invalid; - } - return amf_ue_id_to_ue_index[amf_ue_id]; -} - -void ue_manager::set_amf_ue_id(ue_index_t ue_index, amf_ue_id_t amf_ue_id) -{ - if (ue_index == ue_index_t::invalid) { - logger.error("Can't set AMF UE ID for UE with invalid index."); - return; - } - - ues.at(ue_index).set_amf_ue_id(amf_ue_id); - // Add AMF UE ID to lookup - amf_ue_id_to_ue_index.emplace(amf_ue_id, ue_index); -} - -void ue_manager::transfer_ngap_ue_context(ue_index_t new_ue_index, ue_index_t old_ue_index) -{ - // Update ue index at lookups - srsran_assert(ran_ue_id_to_ue_index.find(find_ran_ue_id(old_ue_index)) != ran_ue_id_to_ue_index.end(), - "ue={} RAN UE ID not found.", - old_ue_index); - ran_ue_id_to_ue_index.at(find_ran_ue_id(old_ue_index)) = new_ue_index; - - srsran_assert(amf_ue_id_to_ue_index.find(find_amf_ue_id(old_ue_index)) != amf_ue_id_to_ue_index.end(), - "ue={} AMF UE ID not found.", - old_ue_index); - amf_ue_id_to_ue_index.at(find_amf_ue_id(old_ue_index)) = new_ue_index; - - // transfer NGAP UE Context to new UE - auto& old_ue = ues.at(old_ue_index); - auto& new_ue = ues.at(new_ue_index); - - new_ue.set_ngap_ue_context(old_ue.get_ngap_ue_context()); - - logger.debug( - "Transferred NGAP UE context from ue={} (ran_ue_id={} amf_ue_id={}) to ue={} (ran_ue_id={} amf_ue_id={}).", - old_ue_index, - old_ue.get_ran_ue_id(), - old_ue.get_amf_ue_id(), - new_ue_index, - new_ue.get_ran_ue_id(), - new_ue.get_amf_ue_id()); - - // Mark NGAP UE as removed - ues.at(old_ue_index).ngap_ue_created = false; -} - // private functions ue_index_t ue_manager::get_next_ue_index(du_index_t du_index) @@ -320,45 +183,9 @@ ue_index_t ue_manager::get_next_ue_index(du_index_t du_index) for (uint16_t i = 0; i < MAX_NOF_UES_PER_DU; i++) { ue_index_t new_ue_index = generate_ue_index(du_index, i); if (ues.find(new_ue_index) == ues.end()) { - logger.debug("Allocating new ue_index={} for du_index={}.", new_ue_index, du_index); + logger.debug("Allocating new ue_index={} for du_index={}", new_ue_index, du_index); return new_ue_index; } } return ue_index_t::invalid; } - -ran_ue_id_t ue_manager::get_next_ran_ue_id() -{ - // Search unallocated UE index - for (uint64_t i = 0; i < MAX_NOF_RAN_UES; i++) { - ran_ue_id_t next_ran_ue_id = uint_to_ran_ue_id(i); - if (ran_ue_id_to_ue_index.find(next_ran_ue_id) == ran_ue_id_to_ue_index.end()) { - return next_ran_ue_id; - } - } - - logger.error("No RAN UE ID available."); - return ran_ue_id_t::invalid; -} - -ran_ue_id_t ue_manager::find_ran_ue_id(ue_index_t ue_index) -{ - for (auto const& it : ran_ue_id_to_ue_index) { - if (it.second == ue_index) { - return it.first; - } - } - logger.error("ue={} RAN UE ID not found.", ue_index); - return ran_ue_id_t::invalid; -} - -amf_ue_id_t ue_manager::find_amf_ue_id(ue_index_t ue_index) -{ - for (auto const& it : amf_ue_id_to_ue_index) { - if (it.second == ue_index) { - return it.first; - } - } - logger.error("ue={} AMF UE ID not found.", ue_index); - return amf_ue_id_t::invalid; -} \ No newline at end of file diff --git a/lib/cu_cp/ue_manager_impl.h b/lib/cu_cp/ue_manager_impl.h index c3e573c235..5341076a27 100644 --- a/lib/cu_cp/ue_manager_impl.h +++ b/lib/cu_cp/ue_manager_impl.h @@ -23,16 +23,26 @@ #pragma once #include "srsran/cu_cp/ue_manager.h" +#include "srsran/support/timers.h" #include namespace srsran { namespace srs_cu_cp { -struct ngap_ue_context_t { - amf_ue_id_t amf_ue_id = amf_ue_id_t::invalid; - ran_ue_id_t ran_ue_id = ran_ue_id_t::invalid; - uint64_t aggregate_maximum_bit_rate_dl = 0; +struct ngap_ue_t { + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier; + ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier; + ngap_du_processor_control_notifier& du_processor_ctrl_notifier; + + ngap_ue_t(ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_, + ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier_, + ngap_du_processor_control_notifier& du_processor_ctrl_notifier_) : + rrc_ue_pdu_notifier(rrc_ue_pdu_notifier_), + rrc_ue_ctrl_notifier(rrc_ue_ctrl_notifier_), + du_processor_ctrl_notifier(du_processor_ctrl_notifier_) + { + } }; class cu_cp_ue : public du_ue, public ngap_ue @@ -60,11 +70,11 @@ class cu_cp_ue : public du_ue, public ngap_ue /// \brief Get the UE index of the UE. ue_index_t get_ue_index() override { return ue_index; } - // du_ue - /// \brief Get the UP resource manager of the UE. up_resource_manager& get_up_resource_manager() override { return *up_mng; } + // du_ue + /// \brief Get the task scheduler of the UE. rrc_ue_task_scheduler& get_task_sched() override { return *task_sched; } @@ -123,79 +133,45 @@ class cu_cp_ue : public du_ue, public ngap_ue // ngap_ue /// \brief Get the RRC UE PDU notifier of the UE. - ngap_rrc_ue_pdu_notifier& get_rrc_ue_pdu_notifier() override { return *rrc_ue_pdu_notifier; } - - /// \brief Get the RRC UE control notifier of the UE. - ngap_rrc_ue_control_notifier& get_rrc_ue_control_notifier() override { return *rrc_ue_ctrl_notifier; } - - /// \brief Get the DU processor control notifier of the UE. - ngap_du_processor_control_notifier& get_du_processor_control_notifier() override + ngap_rrc_ue_pdu_notifier& get_rrc_ue_pdu_notifier() override { - return *du_processor_ctrl_notifier; + srsran_assert(ngap_ue_context.has_value(), "ue={}: NGAP UE was not created", ue_index); + return ngap_ue_context.value().rrc_ue_pdu_notifier; } - /// \brief Get the AMF UE ID of the UE. - amf_ue_id_t get_amf_ue_id() override { return ngap_ue_context.amf_ue_id; } - - /// \brief Get the RAN UE ID of the UE. - ran_ue_id_t get_ran_ue_id() override { return ngap_ue_context.ran_ue_id; } - - /// \brief Get the aggregate maximum bit rate DL of the UE. - uint64_t get_aggregate_maximum_bit_rate_dl() override { return ngap_ue_context.aggregate_maximum_bit_rate_dl; } - - /// \brief Get the NGAP UE Context. - ngap_ue_context_t get_ngap_ue_context() { return ngap_ue_context; } - - /// \brief Set the aggregate maximum bit rate DL of the UE. - /// \param[in] aggregate_maximum_bit_rate_dl Aggregate maximum bit rate DL. - void set_aggregate_maximum_bit_rate_dl(uint64_t aggregate_maximum_bit_rate_dl_) override - { - ngap_ue_context.aggregate_maximum_bit_rate_dl = aggregate_maximum_bit_rate_dl_; - } - - bool du_ue_created = false; - bool ngap_ue_created = false; - - /// \brief Set the RAN UE ID of the UE. - /// \param[in] ran_ue_id_ RAN UE ID of the UE. - void set_ran_ue_id(ran_ue_id_t ran_ue_id_) { ngap_ue_context.ran_ue_id = ran_ue_id_; } - - /// \brief Set the RRC UE PDU notifier of the UE. - /// \param[in] rrc_ue_pdu_notifier_ RRC UE PDU notifier for the UE. - void set_rrc_ue_pdu_notifier(ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_) + /// \brief Get the RRC UE control notifier of the UE. + ngap_rrc_ue_control_notifier& get_rrc_ue_control_notifier() override { - rrc_ue_pdu_notifier = &rrc_ue_pdu_notifier_; + srsran_assert(ngap_ue_context.has_value(), "ue={}: NGAP UE was not created", ue_index); + return ngap_ue_context.value().rrc_ue_ctrl_notifier; } - /// \brief Set the RRC UE control notifier of the UE. - /// \param[in] rrc_ue_ctrl_notifier_ RRC UE control notifier for the UE. - void set_rrc_ue_ctrl_notifier(ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier_) + /// \brief Get the DU processor control notifier of the UE. + ngap_du_processor_control_notifier& get_du_processor_control_notifier() override { - rrc_ue_ctrl_notifier = &rrc_ue_ctrl_notifier_; + srsran_assert(ngap_ue_context.has_value(), "ue={}: NGAP UE was not created", ue_index); + return ngap_ue_context.value().du_processor_ctrl_notifier; } - /// \brief Set the DU processor control notifier of the UE. - /// \param[in] du_processor_ctrl_notifier_ DU processor control notifier for the UE. - void set_du_processor_ctrl_notifier(ngap_du_processor_control_notifier& du_processor_ctrl_notifier_) + /// \brief Add the context for the NGAP UE. + /// \param[in] rrc_ue_pdu_notifier The RRC UE PDU notifier for the UE. + /// \param[in] rrc_ue_ctrl_notifier The RRC UE ctrl notifier for the UE. + /// \param[in] du_processor_ctrl_notifier The DU processor ctrl notifier for the UE. + void add_ngap_ue_context(ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier, + ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier, + ngap_du_processor_control_notifier& du_processor_ctrl_notifier) { - du_processor_ctrl_notifier = &du_processor_ctrl_notifier_; + ngap_ue_context.emplace(rrc_ue_pdu_notifier, rrc_ue_ctrl_notifier, du_processor_ctrl_notifier); } - /// \brief Set the AMF UE ID in the UE. - /// \param[in] amf_ue_id The AMF UE ID to set. - void set_amf_ue_id(amf_ue_id_t amf_ue_id_) { ngap_ue_context.amf_ue_id = amf_ue_id_; } - - /// \brief Set the NGAP UE Context. - /// \param[in] ue_context The NGAP UE Context. - void set_ngap_ue_context(ngap_ue_context_t ue_context) { ngap_ue_context = ue_context; } + bool ngap_ue_created() { return ngap_ue_context.has_value(); } private: // common context - ue_index_t ue_index = ue_index_t::invalid; - - // du ue context + ue_index_t ue_index = ue_index_t::invalid; std::unique_ptr up_mng; + // du ue context du_index_t du_index = du_index_t::invalid; du_cell_index_t pcell_index = du_cell_index_t::invalid; pci_t pci = INVALID_PCI; @@ -206,11 +182,7 @@ class cu_cp_ue : public du_ue, public ngap_ue du_processor_rrc_ue_srb_control_notifier* rrc_ue_srb_notifier = nullptr; // ngap ue context - ngap_ue_context_t ngap_ue_context; - - ngap_rrc_ue_pdu_notifier* rrc_ue_pdu_notifier = nullptr; - ngap_rrc_ue_control_notifier* rrc_ue_ctrl_notifier = nullptr; - ngap_du_processor_control_notifier* du_processor_ctrl_notifier = nullptr; + optional ngap_ue_context; }; class ue_manager : public du_processor_ue_manager, public ngap_ue_manager @@ -221,14 +193,22 @@ class ue_manager : public du_processor_ue_manager, public ngap_ue_manager // common + /// \brief Get the CU-CP UE configuration stored in the UE manager. + /// \return The CU-CP UE configuration. + ue_configuration get_ue_config() override { return ue_config; } + /// \brief Get the UE index of the UE. /// \param[in] pci The PCI of the cell the UE is/was connected to. /// \param[in] c_rnti The RNTI of the UE. ue_index_t get_ue_index(pci_t pci, rnti_t c_rnti) override; - /// \brief Get the CU-CP UE configuration stored in the UE manager. - /// \return The CU-CP UE configuration. - ue_configuration get_ue_config() override { return ue_config; } + /// \brief Remove the UE context with the given UE index. + /// \param[in] ue_index Index of the UE to be removed. + void remove_ue(ue_index_t ue_index) override; + + /// \brief Get the number of UEs. + /// \return Number of UEs. + size_t get_nof_ues() const override { return ues.size(); } // du_processor_ue_manager @@ -241,16 +221,13 @@ class ue_manager : public du_processor_ue_manager, public ngap_ue_manager du_ue* find_ue(ue_index_t ue_index) override; /// \brief Add PCI and C-RNTI to a UE for the given UE index. If the UE can't be found or if a UE with the UE - /// index was already setup, nulltpr is returned. \param[in] ue_index Index of the UE to add the notifiers to. + /// index was already setup, nulltpr is returned. + /// \param[in] ue_index Index of the UE to add the notifiers to. /// \param[in] pci PCI of the cell that the UE is connected to. /// \param[in] rnti RNTI of the UE to be added. /// \return Pointer to the newly added DU UE if successful, nullptr otherwise. du_ue* add_ue(ue_index_t ue_index, pci_t pci, rnti_t rnti) override; - /// \brief Remove the DU UE context with the given UE index. - /// \param[in] ue_index Index of the UE to be removed. - void remove_du_ue(ue_index_t ue_index) override; - /// \brief Find the UE with the given UE index, thats DU context is set up. /// \param[in] ue_index Index of the UE to be found. /// \return Pointer to the DU UE if found, nullptr otherwise. @@ -264,7 +241,7 @@ class ue_manager : public du_processor_ue_manager, public ngap_ue_manager // Search allocated UE indexes for (uint16_t i = 0; i < MAX_NOF_UES_PER_DU; i++) { ue_index_t new_ue_index = generate_ue_index(du_index, i); - if (ues.find(new_ue_index) != ues.end() && ues.at(new_ue_index).du_ue_created) { + if (ues.find(new_ue_index) != ues.end()) { ue_count++; } } @@ -285,10 +262,6 @@ class ue_manager : public du_processor_ue_manager, public ngap_ue_manager ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier_, ngap_du_processor_control_notifier& du_processor_ctrl_notifier_) override; - /// \brief Remove the NGAP UE context with the given UE index. - /// \param[in] ue_index Index of the UE to be removed. - void remove_ngap_ue(ue_index_t ue_index) override; - /// \brief Find the NGAP UE with the given UE index. /// \param[in] ue_index Index of the UE to be found. /// \return Pointer to the NGAP UE if found, nullptr otherwise. @@ -296,47 +269,23 @@ class ue_manager : public du_processor_ue_manager, public ngap_ue_manager /// \brief Get the number of UEs connected to the AMF. /// \return Number of UEs. - size_t get_nof_ngap_ues() override { return ran_ue_id_to_ue_index.size(); } - - /// \brief Get the UE index of the UE for the given RAN UE ID. - /// \param[in] ran_ue_id RAN UE ID of the UE. - /// \return Index of the UE if found, invalid index otherwise. - ue_index_t get_ue_index(ran_ue_id_t ran_ue_id) override; - - /// \brief Get the UE index of the UE for the given AMF UE ID. - /// \param[in] amf_ue_id AMF UE ID of the UE. - /// \return Index of the UE if found, invalid index otherwise. - ue_index_t get_ue_index(amf_ue_id_t amf_ue_id) override; - - /// \brief Set the AMF UE ID of the UE. - /// \param[in] ue_index Index of the UE. - /// \param[in] amf_ue_id The AMF UE ID for the UE. - void set_amf_ue_id(ue_index_t ue_index, amf_ue_id_t amf_ue_id) override; - - /// \brief Transfer the NGAP UE context to a new UE e.g. in case of a reestablishment. - /// \param[in] new_ue_index The index of the new UE. - /// \param[in] old_ue_index The index of the old UE. - void transfer_ngap_ue_context(ue_index_t new_ue_index, ue_index_t old_ue_index) override; + size_t get_nof_ngap_ues() override + { + unsigned ue_count = 0; + // Search allocated UE indexes + for (auto& ue : ues) { + if (ue.second.ngap_ue_created()) { + ue_count++; + } + } + return ue_count; + } private: /// \brief Get the next available UE index. /// \return The UE index. ue_index_t get_next_ue_index(du_index_t du_index); - /// \brief Get the next available RAN UE ID. - /// \return The RAN UE ID. - ran_ue_id_t get_next_ran_ue_id(); - - /// \brief Find the RAN UE ID by a given UE index. - /// \param[in] ue_index The UE index used to find the RAN UE ID. - /// \return The RAN UE ID. - ran_ue_id_t find_ran_ue_id(ue_index_t ue_index); - - /// \brief Find the AMF UE ID by a given UE index. - /// \param[in] ue_index The UE index used to find the AMF UE ID. - /// \return The AMF UE ID. - amf_ue_id_t find_amf_ue_id(ue_index_t ue_index); - void clear_ue() { // TODO @@ -349,9 +298,7 @@ class ue_manager : public du_processor_ue_manager, public ngap_ue_manager std::unordered_map ues; // ues indexed by ue_index // ue index lookups - std::map, ue_index_t> pci_rnti_to_ue_index; // ue_indexes indexed by pci and rnti - std::unordered_map ran_ue_id_to_ue_index; // ue_indexes indexed by ran_ue_ids - std::unordered_map amf_ue_id_to_ue_index; // ue_indexes indexed by amf_ue_ids + std::map, ue_index_t> pci_rnti_to_ue_index; // ue_indexes indexed by pci and rnti }; } // namespace srs_cu_cp diff --git a/lib/cu_cp/up_resource_manager/up_resource_manager_helpers.cpp b/lib/cu_cp/up_resource_manager/up_resource_manager_helpers.cpp index 672d4f037a..941bf24c8f 100644 --- a/lib/cu_cp/up_resource_manager/up_resource_manager_helpers.cpp +++ b/lib/cu_cp/up_resource_manager/up_resource_manager_helpers.cpp @@ -67,7 +67,7 @@ drb_id_t srsran::srs_cu_cp::allocate_drb_id(const up_pdu_session_context_update& const srslog::basic_logger& logger) { if (context.drb_map.size() >= MAX_NOF_DRBS) { - logger.error("No more DRBs available"); + logger.warning("No more DRBs available"); return drb_id_t::invalid; } @@ -78,7 +78,7 @@ drb_id_t srsran::srs_cu_cp::allocate_drb_id(const up_pdu_session_context_update& /// try next new_drb_id = uint_to_drb_id(drb_id_to_uint(new_drb_id) + 1); if (new_drb_id == drb_id_t::invalid) { - logger.error("No more DRBs available"); + logger.warning("No more DRBs available"); return drb_id_t::invalid; } } @@ -109,21 +109,21 @@ bool srsran::srs_cu_cp::is_valid( { // Reject empty setup requests. if (setup_items.empty()) { - logger.error("Received empty setup list"); + logger.warning("Received empty setup list"); return false; } // Reject request if PDU session with same ID already exists. for (const auto& pdu_session : setup_items) { if (context.pdu_sessions.find(pdu_session.pdu_session_id) != context.pdu_sessions.end()) { - logger.error("PDU session ID {} already exists", pdu_session.pdu_session_id); + logger.debug("PDU session ID {} already exists", pdu_session.pdu_session_id); return false; } // Reject request if OoS flow requirements can't be met. for (const auto& qos_flow : pdu_session.qos_flow_setup_request_items) { if (get_five_qi(qos_flow, cfg, logger) == five_qi_t::invalid) { - logger.error("Configuration for {} can't be derived", qos_flow.qos_flow_id); + logger.warning("Configuration for {} can't be derived", qos_flow.qos_flow_id); return false; } } @@ -147,14 +147,14 @@ bool srsran::srs_cu_cp::is_valid(const cu_cp_pdu_session_resource_modify_request for (const auto& pdu_session : pdu.pdu_session_res_modify_items) { // Reject request if PDU session with same ID does not already exists. if (context.pdu_sessions.find(pdu_session.pdu_session_id) == context.pdu_sessions.end()) { - logger.error("Can't modify PDU session ID {} - session doesn't exist", pdu_session.pdu_session_id); + logger.warning("Can't modify PDU session ID {} - session doesn't exist", pdu_session.pdu_session_id); return false; } // Reject request if OoS flow requirements can't be met. for (const auto& qos_flow : pdu_session.transfer.qos_flow_add_or_modify_request_list) { if (get_five_qi(qos_flow, cfg, logger) == five_qi_t::invalid) { - logger.error("QoS flow configuration for flow ID {} can't be derived", qos_flow.qos_flow_id); + logger.warning("QoS flow configuration for flow ID {} can't be derived", qos_flow.qos_flow_id); return false; } } @@ -162,16 +162,16 @@ bool srsran::srs_cu_cp::is_valid(const cu_cp_pdu_session_resource_modify_request // Reject request if QoS flow to remove doesn't exist. for (const auto& qos_flow : pdu_session.transfer.qos_flow_to_release_list) { if (context.qos_flow_map.find(qos_flow.qos_flow_id) == context.qos_flow_map.end()) { - logger.error("{} doesn't exist", qos_flow.qos_flow_id); + logger.warning("{} doesn't exist", qos_flow.qos_flow_id); return false; } } // Reject request if it removes all exisiting QoS flows. if (pdu_session.transfer.qos_flow_to_release_list.size() >= context.qos_flow_map.size()) { - logger.error("Modification requests tries to remove {} from {} existing QoS flows.", - pdu_session.transfer.qos_flow_to_release_list.size(), - context.qos_flow_map.size()); + logger.warning("Modification requests tries to remove {} from {} existing QoS flows.", + pdu_session.transfer.qos_flow_to_release_list.size(), + context.qos_flow_map.size()); return false; } } @@ -194,7 +194,7 @@ bool srsran::srs_cu_cp::is_valid(const cu_cp_pdu_session_resource_release_comman for (const auto& pdu_session : pdu.pdu_session_res_to_release_list_rel_cmd) { // Reject request if PDU session with same ID does not exist. if (context.pdu_sessions.find(pdu_session.pdu_session_id) == context.pdu_sessions.end()) { - logger.error("Can't release PDU session ID {} - session doesn't exist", pdu_session.pdu_session_id); + logger.warning("Can't release PDU session ID {} - session doesn't exist", pdu_session.pdu_session_id); return false; } } @@ -217,7 +217,7 @@ drb_id_t allocate_qos_flow(up_pdu_session_context_update& new_session_contex // potential optimization to support more QoS flows is to map non-GPB flows onto existing DRBs. drb_id_t drb_id = allocate_drb_id(new_session_context, full_context, config_update, logger); if (drb_id == drb_id_t::invalid) { - logger.error("No more DRBs available"); + logger.warning("No more DRBs available"); return drb_id; } @@ -294,19 +294,19 @@ five_qi_t srsran::srs_cu_cp::get_five_qi(const cu_cp_qos_flow_add_or_mod_item& q if (qos_params.qos_characteristics.dyn_5qi.value().five_qi.has_value()) { five_qi = qos_params.qos_characteristics.dyn_5qi.value().five_qi.value(); } else { - logger.error("Dynamic 5QI without 5QI not supported"); + logger.warning("Dynamic 5QI without 5QI not supported"); return five_qi_t::invalid; } } else if (qos_params.qos_characteristics.non_dyn_5qi.has_value()) { five_qi = qos_params.qos_characteristics.non_dyn_5qi.value().five_qi; } else { - logger.error("Invalid QoS characteristics. Either dynamic or non-dynamic 5QI must be set"); + logger.warning("Invalid QoS characteristics. Either dynamic or non-dynamic 5QI must be set"); return five_qi_t::invalid; } // Check if we have a valid config for the selected 5QI. if (!is_valid(five_qi, cfg, logger)) { - logger.error("No valid config for {}", five_qi); + logger.warning("No valid config for {}", five_qi); return five_qi_t::invalid; } @@ -329,7 +329,7 @@ up_config_update srsran::srs_cu_cp::calculate_update(const cu_cp_pdu_session_res for (const auto& flow_item : modify_item.transfer.qos_flow_add_or_modify_request_list) { auto drb_id = allocate_qos_flow(ctxt_update, flow_item, update, context, cfg, logger); if (drb_id == drb_id_t::invalid) { - logger.error("Couldn't allocate {}", flow_item.qos_flow_id); + logger.warning("Couldn't allocate {}", flow_item.qos_flow_id); update.pdu_sessions_failed_to_modify_list.push_back(modify_item.pdu_session_id); } else { srsran_assert(context.drb_map.find(drb_id) != context.drb_map.end() || diff --git a/lib/cu_up/cu_up_impl.cpp b/lib/cu_up/cu_up_impl.cpp index 08ef8eaa5c..b9b82738be 100644 --- a/lib/cu_up/cu_up_impl.cpp +++ b/lib/cu_up/cu_up_impl.cpp @@ -69,6 +69,7 @@ cu_up::cu_up(const cu_up_configuration& config_) : cfg(config_), main_ctrl_loop( udp_network_gateway_config ngu_gw_config = {}; ngu_gw_config.bind_address = cfg.net_cfg.n3_bind_addr; ngu_gw_config.bind_port = cfg.net_cfg.n3_bind_port; + ngu_gw_config.rx_max_mmsg = cfg.net_cfg.n3_rx_max_mmsg; // other params udp_network_gateway_creation_message ngu_gw_msg = {ngu_gw_config, gw_data_gtpu_demux_adapter}; ngu_gw = create_udp_network_gateway(ngu_gw_msg); @@ -118,6 +119,14 @@ cu_up::cu_up(const cu_up_configuration& config_) : cfg(config_), main_ctrl_loop( *cfg.gtpu_pcap, *cfg.cu_up_executor, logger); + + // Start statistics report timer + if (cfg.statistics_report_period.count() > 0) { + statistics_report_timer = cfg.timers->create_unique_timer(*cfg.cu_up_executor); + statistics_report_timer.set(cfg.statistics_report_period, + [this](timer_id_t /*tid*/) { on_statistics_report_timer_expired(); }); + statistics_report_timer.run(); + } } void cu_up::start() @@ -424,3 +433,14 @@ void cu_up::on_e1ap_connection_drop() { e1ap_connected = false; } + +void cu_up::on_statistics_report_timer_expired() +{ + // Log statistics + logger.debug("num_e1ap_ues={} num_cu_up_ues={}", e1ap->get_nof_ues(), ue_mng->get_nof_ues()); + + // Restart timer + statistics_report_timer.set(cfg.statistics_report_period, + [this](timer_id_t /*tid*/) { on_statistics_report_timer_expired(); }); + statistics_report_timer.run(); +} diff --git a/lib/cu_up/cu_up_impl.h b/lib/cu_up/cu_up_impl.h index b98d99eafe..6199320343 100644 --- a/lib/cu_up/cu_up_impl.h +++ b/lib/cu_up/cu_up_impl.h @@ -32,7 +32,7 @@ #include "srsran/gateways/udp_network_gateway.h" #include "srsran/gtpu/gtpu_echo.h" #include "srsran/gtpu/gtpu_teid_pool.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/executors/task_executor.h" #include #include @@ -77,6 +77,8 @@ class cu_up final : public cu_up_interface gtpu_demux_rx_upper_layer_interface& get_ngu_pdu_handler() override { return *ngu_demux; } private: + void on_statistics_report_timer_expired(); + cu_up_configuration cfg; // logger @@ -100,7 +102,9 @@ class cu_up final : public cu_up_interface bool running{false}; // Handler for CU-UP tasks. - async_task_sequencer main_ctrl_loop; + fifo_async_task_scheduler main_ctrl_loop; + + unique_timer statistics_report_timer; }; } // namespace srs_cu_up diff --git a/lib/cu_up/ue_manager.cpp b/lib/cu_up/ue_manager.cpp index 14f94ec368..a03a3aaa0f 100644 --- a/lib/cu_up/ue_manager.cpp +++ b/lib/cu_up/ue_manager.cpp @@ -110,8 +110,3 @@ ue_index_t ue_manager::get_next_ue_index() } return INVALID_UE_INDEX; } - -size_t ue_manager::get_nof_ues() -{ - return ue_db.size(); -} diff --git a/lib/cu_up/ue_manager.h b/lib/cu_up/ue_manager.h index 4d5cf27c0c..ee9a664a65 100644 --- a/lib/cu_up/ue_manager.h +++ b/lib/cu_up/ue_manager.h @@ -52,7 +52,7 @@ class ue_manager : public ue_manager_ctrl ue_context* add_ue(const ue_context_cfg& cfg) override; void remove_ue(ue_index_t ue_index) override; ue_context* find_ue(ue_index_t ue_index) override; - size_t get_nof_ues() override; + size_t get_nof_ues() const override { return ue_db.size(); }; private: /// \brief Get the next available UE index. diff --git a/lib/cu_up/ue_manager_interfaces.h b/lib/cu_up/ue_manager_interfaces.h index 5d5f9df6e3..2a03e6ffd5 100644 --- a/lib/cu_up/ue_manager_interfaces.h +++ b/lib/cu_up/ue_manager_interfaces.h @@ -37,7 +37,7 @@ class ue_manager_ctrl virtual ue_context* add_ue(const ue_context_cfg& ue_cfg) = 0; virtual void remove_ue(ue_index_t ue_index) = 0; virtual ue_context* find_ue(ue_index_t ue_index) = 0; - virtual size_t get_nof_ues() = 0; + virtual size_t get_nof_ues() const = 0; }; } // namespace srs_cu_up diff --git a/lib/du_high/du_high_executor_strategies.h b/lib/du_high/du_high_executor_strategies.h index 189ebd25e6..0ea7f9e023 100644 --- a/lib/du_high/du_high_executor_strategies.h +++ b/lib/du_high/du_high_executor_strategies.h @@ -31,60 +31,131 @@ namespace srsran { class index_based_ue_executor_mapper final : public du_high_ue_executor_mapper { public: - index_based_ue_executor_mapper(const std::initializer_list& execs_) : - execs(execs_.begin(), execs_.end()) + index_based_ue_executor_mapper(const std::vector& ctrl_execs, + const std::vector& ul_execs = {}, + const std::vector& dl_execs = {}) : + execs(ctrl_execs.size()) { + srsran_assert(ctrl_execs.size() > 0, "At least one control executor must be specified"); + srsran_assert(ul_execs.size() == 0 or ul_execs.size() == ctrl_execs.size(), + "If specified, the number of UE UL executors must be equal to the number of control executors"); + srsran_assert(dl_execs.size() == 0 or dl_execs.size() == ctrl_execs.size(), + "If specified, the number of UE DL executors must be equal to the number of control executors"); + + for (unsigned i = 0; i != ctrl_execs.size(); ++i) { + execs[i].ctrl_exec = ctrl_execs[i]; + // If UL executors not specified, we use the control executor. + execs[i].ul_exec = ul_execs.empty() ? ctrl_execs[i] : ul_execs[i]; + // If DL executors not specified, we use the DL executor (that might be the control executor if the DL executor + // was not specified). + execs[i].dl_exec = dl_execs.empty() ? execs[i].ul_exec : dl_execs[i]; + } + } + + void rebind_executors(du_ue_index_t ue_index, du_cell_index_t pcell_index) override + { + // Do nothing. } - task_executor& rebind_executor(du_ue_index_t ue_index, du_cell_index_t pcell_index) override + task_executor& ctrl_executor(du_ue_index_t ue_index) override { - // Static lookup - srsran_sanity_check(is_du_ue_index_valid(ue_index), "Invalid ueId={}", ue_index); - return executor(ue_index); + ue_index = ue_index < MAX_NOF_DU_UES ? ue_index : to_du_ue_index(0); + return *execs[ue_index % execs.size()].ctrl_exec; } - task_executor& executor(du_ue_index_t ue_index) override + task_executor& f1u_dl_pdu_executor(du_ue_index_t ue_index) override { ue_index = ue_index < MAX_NOF_DU_UES ? ue_index : to_du_ue_index(0); - return *execs[ue_index % execs.size()]; + return *execs[ue_index % execs.size()].dl_exec; + } + + task_executor& mac_ul_pdu_executor(du_ue_index_t ue_index) override + { + ue_index = ue_index < MAX_NOF_DU_UES ? ue_index : to_du_ue_index(0); + return *execs[ue_index % execs.size()].ul_exec; } private: - std::vector execs; + struct ue_executors { + task_executor* ctrl_exec; + task_executor* ul_exec; + task_executor* dl_exec; + }; + + std::vector execs; }; /// L2 UL executor mapper that maps UEs based on their PCell. class pcell_ue_executor_mapper final : public du_high_ue_executor_mapper { public: - explicit pcell_ue_executor_mapper(const std::initializer_list& execs_) : - execs(execs_.begin(), execs_.end()) + explicit pcell_ue_executor_mapper(const std::vector& ctrl_execs, + const std::vector& ul_execs = {}, + const std::vector& dl_execs = {}) : + execs(ctrl_execs.size()) { + srsran_assert(not ctrl_execs.empty(), "At least one control executor must be specified"); + srsran_assert(ul_execs.empty() or ul_execs.size() == ctrl_execs.size(), + "If specified, the number of UE UL executors must be equal to the number of control executors"); + srsran_assert(dl_execs.empty() or dl_execs.size() == ctrl_execs.size(), + "If specified, the number of UE DL executors must be equal to the number of control executors"); + + for (unsigned i = 0; i != ctrl_execs.size(); ++i) { + execs[i].ctrl_exec = ctrl_execs[i]; + // If UL executors not specified, we use the control executor. + execs[i].ul_exec = ul_execs.empty() ? ctrl_execs[i] : ul_execs[i]; + // If DL executors not specified, we use the DL executor (that might be the control executor if the DL executor + // was not specified). + execs[i].dl_exec = dl_execs.empty() ? execs[i].ul_exec : dl_execs[i]; + } + // Initialize executors in a round-robin fashion. - unsigned count = 0; - for (auto& rnti_exec : ue_idx_to_exec) { - rnti_exec = execs[count % execs.size()]; - count++; + for (unsigned i = 0; i != MAX_NOF_DU_UES; ++i) { + ue_idx_to_exec_index[i] = i % execs.size(); } } - task_executor& rebind_executor(du_ue_index_t ue_index, du_cell_index_t pcell_index) override + void rebind_executors(du_ue_index_t ue_index, du_cell_index_t pcell_index) override { srsran_sanity_check(is_du_ue_index_valid(ue_index), "Invalid ue id={}", ue_index); - ue_idx_to_exec[ue_index] = execs[pcell_index % execs.size()]; - return *ue_idx_to_exec[ue_index]; + ue_idx_to_exec_index[ue_index] = pcell_index % execs.size(); + } + + task_executor& ctrl_executor(du_ue_index_t ue_index) override + { + if (ue_index < MAX_NOF_DU_UES) { + return *execs[ue_idx_to_exec_index[ue_index]].ctrl_exec; + } + return *execs[0].ctrl_exec; } - task_executor& executor(du_ue_index_t ue_index) override + task_executor& f1u_dl_pdu_executor(du_ue_index_t ue_index) override { - return *ue_idx_to_exec[std::min(ue_index, MAX_NOF_DU_UES)]; + if (ue_index < MAX_NOF_DU_UES) { + return *execs[ue_idx_to_exec_index[ue_index]].dl_exec; + } + return *execs[0].dl_exec; + } + + task_executor& mac_ul_pdu_executor(du_ue_index_t ue_index) override + { + if (ue_index < MAX_NOF_DU_UES) { + return *execs[ue_idx_to_exec_index[ue_index]].ul_exec; + } + return *execs[0].ul_exec; } private: - std::vector execs; + struct ue_executors { + task_executor* ctrl_exec; + task_executor* ul_exec; + task_executor* dl_exec; + }; + + std::vector execs; /// Map of ue indexes to executors. The last position is used when the UE has no ue_index yet assigned. - std::array ue_idx_to_exec; + std::array ue_idx_to_exec_index; }; /// \brief Mapper of task executors used by the MAC DL, RLC DL and MAC scheduler for low-latency tasks. The task diff --git a/lib/du_high/mac_test_mode_adapter.cpp b/lib/du_high/mac_test_mode_adapter.cpp index f88f087185..37b6eef242 100644 --- a/lib/du_high/mac_test_mode_adapter.cpp +++ b/lib/du_high/mac_test_mode_adapter.cpp @@ -309,7 +309,7 @@ async_task mac_test_mode_adapter::handle_ue_delete_reque return mac_adapted->get_ue_configurator().handle_ue_delete_request(cfg); } -void mac_test_mode_adapter::handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) +bool mac_test_mode_adapter::handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) { - mac_adapted->get_ue_configurator().handle_ul_ccch_msg(ue_index, std::move(pdu)); + return mac_adapted->get_ue_configurator().handle_ul_ccch_msg(ue_index, std::move(pdu)); } diff --git a/lib/du_high/mac_test_mode_adapter.h b/lib/du_high/mac_test_mode_adapter.h index ffb6c9a2f5..b1d9d159e4 100644 --- a/lib/du_high/mac_test_mode_adapter.h +++ b/lib/du_high/mac_test_mode_adapter.h @@ -69,7 +69,7 @@ class mac_test_mode_adapter final : public mac_interface, async_task handle_ue_reconfiguration_request(const mac_ue_reconfiguration_request& cfg) override; async_task handle_ue_delete_request(const mac_ue_delete_request& cfg) override; - void handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override; + bool handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override; std::vector adapt_bearers(const std::vector& orig_bearers); diff --git a/lib/du_manager/converters/asn1_rrc_config_helpers.cpp b/lib/du_manager/converters/asn1_rrc_config_helpers.cpp index 8a6ee6c5e6..1c36e2ad2e 100644 --- a/lib/du_manager/converters/asn1_rrc_config_helpers.cpp +++ b/lib/du_manager/converters/asn1_rrc_config_helpers.cpp @@ -375,9 +375,61 @@ asn1::rrc_nr::bwp_ul_common_s srsran::srs_du::make_asn1_rrc_initial_up_bwp(const rach.rach_cfg_generic.msg1_freq_start = static_cast(rach_cfg.rach_cfg_generic.msg1_frequency_start); rach.rach_cfg_generic.zero_correlation_zone_cfg = static_cast(rach_cfg.rach_cfg_generic.zero_correlation_zone_config); - rach.rach_cfg_generic.preamb_rx_target_pwr = rach_cfg.rach_cfg_generic.preamble_rx_target_pw.to_int(); - rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n7; - rach.rach_cfg_generic.pwr_ramp_step.value = asn1::rrc_nr::rach_cfg_generic_s::pwr_ramp_step_opts::db4; + rach.rach_cfg_generic.preamb_rx_target_pwr = rach_cfg.rach_cfg_generic.preamble_rx_target_pw.to_int(); + switch (rach_cfg.rach_cfg_generic.preamble_trans_max) { + case 3: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n3; + break; + case 4: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n4; + break; + case 5: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n5; + break; + case 6: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n6; + break; + case 7: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n7; + break; + case 8: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n8; + break; + case 10: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n10; + break; + case 20: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n20; + break; + case 50: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n50; + break; + case 100: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n100; + break; + case 200: + rach.rach_cfg_generic.preamb_trans_max.value = asn1::rrc_nr::rach_cfg_generic_s::preamb_trans_max_opts::n200; + break; + default: + report_fatal_error("Invalid preamble transmission max value"); + } + switch (rach_cfg.rach_cfg_generic.power_ramping_step_db) { + case 0: + rach.rach_cfg_generic.pwr_ramp_step.value = asn1::rrc_nr::rach_cfg_generic_s::pwr_ramp_step_opts::db0; + break; + case 2: + rach.rach_cfg_generic.pwr_ramp_step.value = asn1::rrc_nr::rach_cfg_generic_s::pwr_ramp_step_opts::db2; + break; + case 4: + rach.rach_cfg_generic.pwr_ramp_step.value = asn1::rrc_nr::rach_cfg_generic_s::pwr_ramp_step_opts::db4; + break; + case 6: + rach.rach_cfg_generic.pwr_ramp_step.value = asn1::rrc_nr::rach_cfg_generic_s::pwr_ramp_step_opts::db6; + break; + default: + report_fatal_error("Invalid power ramping step value"); + } + bool success = asn1::number_to_enum(rach.rach_cfg_generic.ra_resp_win, rach_cfg.rach_cfg_generic.ra_resp_window); srsran_assert(success, "Invalid ra-WindowSize"); if (rach_cfg.total_nof_ra_preambles.has_value()) { @@ -847,9 +899,9 @@ void calculate_pdsch_config_diff(asn1::rrc_nr::pdsch_cfg_s& out, const pdsch_con // TODO: Remaining. } -void calculate_bwp_dl_dedicated_diff(asn1::rrc_nr::bwp_dl_ded_s& out, - const bwp_downlink_dedicated& src, - const bwp_downlink_dedicated& dest) +static bool calculate_bwp_dl_dedicated_diff(asn1::rrc_nr::bwp_dl_ded_s& out, + const bwp_downlink_dedicated& src, + const bwp_downlink_dedicated& dest) { if ((dest.pdcch_cfg.has_value() && not src.pdcch_cfg.has_value()) || (dest.pdcch_cfg.has_value() && src.pdcch_cfg.has_value() && dest.pdcch_cfg != src.pdcch_cfg)) { @@ -873,6 +925,8 @@ void calculate_bwp_dl_dedicated_diff(asn1::rrc_nr::bwp_dl_ded_s& out, out.pdsch_cfg.set_release(); } // TODO: sps-Config and radioLinkMonitoringConfig. + + return out.pdcch_cfg_present || out.pdsch_cfg_present; } asn1::rrc_nr::pucch_res_set_s srsran::srs_du::make_asn1_rrc_pucch_resource_set(const pucch_resource_set& cfg) @@ -2031,9 +2085,9 @@ void calculate_srs_config_diff(asn1::rrc_nr::srs_cfg_s& out, const srs_config& s } } -void calculate_bwp_ul_dedicated_diff(asn1::rrc_nr::bwp_ul_ded_s& out, - const bwp_uplink_dedicated& src, - const bwp_uplink_dedicated& dest) +static bool calculate_bwp_ul_dedicated_diff(asn1::rrc_nr::bwp_ul_ded_s& out, + const bwp_uplink_dedicated& src, + const bwp_uplink_dedicated& dest) { if ((dest.pucch_cfg.has_value() && not src.pucch_cfg.has_value()) || (dest.pucch_cfg.has_value() && src.pucch_cfg.has_value() && dest.pucch_cfg != src.pucch_cfg)) { @@ -2067,14 +2121,18 @@ void calculate_bwp_ul_dedicated_diff(asn1::rrc_nr::bwp_ul_ded_s& out, out.srs_cfg.set_release(); } // TODO: Remaining. + + return out.pucch_cfg_present || out.pusch_cfg_present || out.srs_cfg_present; } -void calculate_uplink_config_diff(asn1::rrc_nr::ul_cfg_s& out, const uplink_config& src, const uplink_config& dest) +static bool +calculate_uplink_config_diff(asn1::rrc_nr::ul_cfg_s& out, const uplink_config& src, const uplink_config& dest) { - out.init_ul_bwp_present = true; - calculate_bwp_ul_dedicated_diff(out.init_ul_bwp, src.init_ul_bwp, dest.init_ul_bwp); + out.init_ul_bwp_present = calculate_bwp_ul_dedicated_diff(out.init_ul_bwp, src.init_ul_bwp, dest.init_ul_bwp); // TODO: Remaining. + + return out.init_ul_bwp_present; } void calculate_pdsch_serving_cell_cfg_diff(asn1::rrc_nr::pdsch_serving_cell_cfg_s& out, @@ -2171,17 +2229,15 @@ void calculate_pdsch_serving_cell_cfg_diff(asn1::rrc_nr::pdsch_serving_cell_cfg_ } } -void calculate_serving_cell_config_diff(asn1::rrc_nr::serving_cell_cfg_s& out, - const serving_cell_config& src, - const serving_cell_config& dest) +static bool calculate_serving_cell_config_diff(asn1::rrc_nr::serving_cell_cfg_s& out, + const serving_cell_config& src, + const serving_cell_config& dest) { - out.init_dl_bwp_present = true; - calculate_bwp_dl_dedicated_diff(out.init_dl_bwp, src.init_dl_bwp, dest.init_dl_bwp); + out.init_dl_bwp_present = calculate_bwp_dl_dedicated_diff(out.init_dl_bwp, src.init_dl_bwp, dest.init_dl_bwp); // UplinkConfig. if (dest.ul_config.has_value()) { - out.ul_cfg_present = true; - calculate_uplink_config_diff( + out.ul_cfg_present = calculate_uplink_config_diff( out.ul_cfg, src.ul_config.has_value() ? src.ul_config.value() : uplink_config{}, dest.ul_config.value()); } @@ -2213,6 +2269,9 @@ void calculate_serving_cell_config_diff(asn1::rrc_nr::serving_cell_cfg_s& out, // TAG-ID. out.tag_id = dest.tag_id; + + return out.init_dl_bwp_present || out.ul_cfg_present || out.pdsch_serving_cell_cfg_present || + out.csi_meas_cfg_present; } asn1::rrc_nr::sched_request_to_add_mod_s @@ -2528,9 +2587,9 @@ void make_asn1_rrc_phr_config(asn1::rrc_nr::phr_cfg_s& out, const phr_config& cf } } -void calculate_mac_cell_group_config_diff(asn1::rrc_nr::mac_cell_group_cfg_s& out, - const mac_cell_group_config& src, - const mac_cell_group_config& dest) +static bool calculate_mac_cell_group_config_diff(asn1::rrc_nr::mac_cell_group_cfg_s& out, + const mac_cell_group_config& src, + const mac_cell_group_config& dest) { calculate_addmodremlist_diff( out.sched_request_cfg.sched_request_to_add_mod_list, @@ -2570,6 +2629,8 @@ void calculate_mac_cell_group_config_diff(asn1::rrc_nr::mac_cell_group_cfg_s& ou } out.skip_ul_tx_dyn = dest.skip_uplink_tx_dynamic; + + return out.sched_request_cfg_present || out.bsr_cfg_present || out.tag_cfg_present || out.phr_cfg_present; } void srsran::srs_du::calculate_cell_group_config_diff(asn1::rrc_nr::cell_group_cfg_s& out, @@ -2584,18 +2645,17 @@ void srsran::srs_du::calculate_cell_group_config_diff(asn1::rrc_nr::cell_group_c [](const rlc_bearer_config& b) { return make_asn1_rrc_rlc_bearer(b); }, [](const rlc_bearer_config& b) { return (uint8_t)b.lcid; }); - out.sp_cell_cfg_present = true; - out.sp_cell_cfg.serv_cell_idx_present = false; if (dest.cells.contains(0)) { - out.sp_cell_cfg.serv_cell_idx = dest.cells[0].serv_cell_idx; - out.sp_cell_cfg.sp_cell_cfg_ded_present = true; - calculate_serving_cell_config_diff(out.sp_cell_cfg.sp_cell_cfg_ded, - src.cells.contains(0) ? src.cells[0].serv_cell_cfg : serving_cell_config{}, - dest.cells[0].serv_cell_cfg); + out.sp_cell_cfg.sp_cell_cfg_ded_present = + calculate_serving_cell_config_diff(out.sp_cell_cfg.sp_cell_cfg_ded, + src.cells.contains(0) ? src.cells[0].serv_cell_cfg : serving_cell_config{}, + dest.cells[0].serv_cell_cfg); + + out.sp_cell_cfg_present = out.sp_cell_cfg.sp_cell_cfg_ded_present; } - out.mac_cell_group_cfg_present = true; - calculate_mac_cell_group_config_diff(out.mac_cell_group_cfg, src.mcg_cfg, dest.mcg_cfg); + out.mac_cell_group_cfg_present = + calculate_mac_cell_group_config_diff(out.mac_cell_group_cfg, src.mcg_cfg, dest.mcg_cfg); out.phys_cell_group_cfg_present = true; if (dest.pcg_cfg.p_nr_fr1.has_value()) { diff --git a/lib/du_manager/converters/f1ap_configuration_helpers.cpp b/lib/du_manager/converters/f1ap_configuration_helpers.cpp index 0eae0c183c..0fe908b0b6 100644 --- a/lib/du_manager/converters/f1ap_configuration_helpers.cpp +++ b/lib/du_manager/converters/f1ap_configuration_helpers.cpp @@ -278,16 +278,16 @@ static asn1::rrc_nr::serving_cell_cfg_common_sib_s make_asn1_rrc_cell_serving_ce return cell; } -asn1::rrc_nr::sib1_s make_asn1_rrc_cell_sib1(const du_cell_config& du_cfg) +static asn1::rrc_nr::sib1_s make_asn1_rrc_cell_sib1(const du_cell_config& du_cfg) { using namespace asn1::rrc_nr; sib1_s sib1; sib1.cell_sel_info_present = true; - sib1.cell_sel_info.q_rx_lev_min = du_cfg.cell_sel_info.q_rx_lev_min.to_int(); + sib1.cell_sel_info.q_rx_lev_min = du_cfg.cell_sel_info.q_rx_lev_min.value(); sib1.cell_sel_info.q_qual_min_present = true; - sib1.cell_sel_info.q_qual_min = du_cfg.cell_sel_info.q_qual_min.to_int(); + sib1.cell_sel_info.q_qual_min = du_cfg.cell_sel_info.q_qual_min.value(); sib1.cell_access_related_info.plmn_id_info_list.resize(1); sib1.cell_access_related_info.plmn_id_info_list[0].plmn_id_list.resize(1); @@ -313,17 +313,51 @@ asn1::rrc_nr::sib1_s make_asn1_rrc_cell_sib1(const du_cell_config& du_cfg) sib1.conn_est_fail_ctrl.conn_est_fail_offset_present = true; sib1.conn_est_fail_ctrl.conn_est_fail_offset = 1; + if (du_cfg.si_config.has_value()) { + sib1.si_sched_info_present = true; + bool ret = asn1::number_to_enum(sib1.si_sched_info.si_win_len, du_cfg.si_config.value().si_window_len_slots); + srsran_assert(ret, "Invalid SI window length"); + sib1.si_sched_info.sched_info_list.resize(du_cfg.si_config->si_sched_info.size()); + for (unsigned i = 0; i != du_cfg.si_config->si_sched_info.size(); ++i) { + const auto& cfg_si = du_cfg.si_config->si_sched_info[i]; + auto& asn1_si = sib1.si_sched_info.sched_info_list[i]; + asn1_si.si_broadcast_status.value = sched_info_s::si_broadcast_status_opts::broadcasting; + ret = asn1::number_to_enum(asn1_si.si_periodicity, cfg_si.si_period_radio_frames); + srsran_assert(ret, "Invalid SI period"); + asn1_si.sib_map_info.resize(cfg_si.sib_mapping_info.size()); + for (unsigned j = 0; j != cfg_si.sib_mapping_info.size(); ++j) { + const uint8_t sib_id = static_cast(cfg_si.sib_mapping_info[j]); + ret = asn1::number_to_enum(asn1_si.sib_map_info[j].type, sib_id); + srsran_assert(ret, "Invalid SIB id {}", sib_id); + } + } + } + sib1.serving_cell_cfg_common_present = true; sib1.serving_cell_cfg_common = make_asn1_rrc_cell_serving_cell_common(du_cfg); - sib1.ue_timers_and_consts_present = true; - sib1.ue_timers_and_consts.t300.value = ue_timers_and_consts_s::t300_opts::ms1000; - sib1.ue_timers_and_consts.t301.value = ue_timers_and_consts_s::t301_opts::ms1000; - sib1.ue_timers_and_consts.t310.value = ue_timers_and_consts_s::t310_opts::ms1000; - sib1.ue_timers_and_consts.n310.value = ue_timers_and_consts_s::n310_opts::n1; - sib1.ue_timers_and_consts.t311.value = ue_timers_and_consts_s::t311_opts::ms30000; - sib1.ue_timers_and_consts.n311.value = ue_timers_and_consts_s::n311_opts::n1; - sib1.ue_timers_and_consts.t319.value = ue_timers_and_consts_s::t319_opts::ms1000; + sib1.ue_timers_and_consts_present = true; + + bool ret = asn1::number_to_enum(sib1.ue_timers_and_consts.t300, du_cfg.ue_timers_and_constants.t300.count()); + srsran_assert(ret, "Invalid value for T300: {}", du_cfg.ue_timers_and_constants.t300.count()); + + ret = asn1::number_to_enum(sib1.ue_timers_and_consts.t301, du_cfg.ue_timers_and_constants.t301.count()); + srsran_assert(ret, "Invalid value for T301: {}", du_cfg.ue_timers_and_constants.t301.count()); + + ret = asn1::number_to_enum(sib1.ue_timers_and_consts.t310, du_cfg.ue_timers_and_constants.t310.count()); + srsran_assert(ret, "Invalid value for T310: {}", du_cfg.ue_timers_and_constants.t310.count()); + + ret = asn1::number_to_enum(sib1.ue_timers_and_consts.n310, du_cfg.ue_timers_and_constants.n310); + srsran_assert(ret, "Invalid value for N310: {}", du_cfg.ue_timers_and_constants.n310); + + ret = asn1::number_to_enum(sib1.ue_timers_and_consts.t311, du_cfg.ue_timers_and_constants.t311.count()); + srsran_assert(ret, "Invalid value for T311: {}", du_cfg.ue_timers_and_constants.t311.count()); + + ret = asn1::number_to_enum(sib1.ue_timers_and_consts.n311, du_cfg.ue_timers_and_constants.n311); + srsran_assert(ret, "Invalid value for N311: {}", du_cfg.ue_timers_and_constants.n311); + + ret = asn1::number_to_enum(sib1.ue_timers_and_consts.t319, du_cfg.ue_timers_and_constants.t319.count()); + srsran_assert(ret, "Invalid value for T319: {}", du_cfg.ue_timers_and_constants.t319.count()); return sib1; } @@ -344,15 +378,79 @@ byte_buffer srsran::srs_du::make_asn1_rrc_cell_sib1_buffer(const du_cell_config& return buf; } -byte_buffer srsran::srs_du::make_asn1_rrc_cell_bcch_dl_sch_msg(const du_cell_config& du_cfg) +static asn1::rrc_nr::sys_info_ies_s::sib_type_and_info_item_c_ make_asn1_rrc_sib_item(const sib_info& sib) { - byte_buffer buf; - asn1::bit_ref bref{buf}; - asn1::rrc_nr::bcch_dl_sch_msg_s msg; - msg.msg.set_c1().set_sib_type1() = make_asn1_rrc_cell_sib1(du_cfg); - asn1::SRSASN_CODE ret = msg.pack(bref); - srsran_assert(ret == asn1::SRSASN_SUCCESS, "Failed to pack SIB1"); - return buf; + using namespace asn1::rrc_nr; + + sys_info_ies_s::sib_type_and_info_item_c_ ret; + + switch (get_sib_info_type(sib)) { + case sib_type::sib2: { + const sib2_info& cfg = variant_get(sib); + sib2_s& out_sib = ret.set_sib2(); + if (cfg.nof_ssbs_to_average.has_value()) { + out_sib.cell_resel_info_common.nrof_ss_blocks_to_average_present = true; + out_sib.cell_resel_info_common.nrof_ss_blocks_to_average = cfg.nof_ssbs_to_average.value(); + } + // TODO + } break; + case sib_type::sib19: { + const sib19_info& cfg = variant_get(sib); + sib19_r17_s& out_sib = ret.set_sib19_v1700(); + if (cfg.distance_thres.has_value()) { + out_sib.distance_thresh_r17_present = true; + out_sib.distance_thresh_r17 = cfg.distance_thres.value(); + } + // TODO + } break; + default: + srsran_assertion_failure("Invalid SIB type"); + } + + return ret; +} + +std::vector srsran::srs_du::make_asn1_rrc_cell_bcch_dl_sch_msgs(const du_cell_config& du_cfg) +{ + std::vector msgs; + + // Pack SIB1. + { + byte_buffer buf; + asn1::bit_ref bref{buf}; + asn1::rrc_nr::bcch_dl_sch_msg_s msg; + msg.msg.set_c1().set_sib_type1() = make_asn1_rrc_cell_sib1(du_cfg); + asn1::SRSASN_CODE ret = msg.pack(bref); + srsran_assert(ret == asn1::SRSASN_SUCCESS, "Failed to pack SIB1"); + msgs.push_back(std::move(buf)); + } + + // Pack SI messages. + if (du_cfg.si_config.has_value()) { + const auto& sibs = du_cfg.si_config.value().sibs; + + for (const auto& si_sched : du_cfg.si_config.value().si_sched_info) { + byte_buffer buf; + asn1::bit_ref bref{buf}; + asn1::rrc_nr::bcch_dl_sch_msg_s msg; + asn1::rrc_nr::sys_info_ies_s& si_ies = msg.msg.set_c1().set_sys_info().crit_exts.set_sys_info(); + + // Search for SIB contained in this SI message. + for (sib_type sib_id : si_sched.sib_mapping_info) { + auto it = std::find_if( + sibs.begin(), sibs.end(), [sib_id](const sib_info& sib) { return get_sib_info_type(sib) == sib_id; }); + srsran_assert(it != sibs.end(), "SIB{} in SIB mapping info has no defined config", (unsigned)sib_id); + + si_ies.sib_type_and_info.push_back(make_asn1_rrc_sib_item(*it)); + } + + asn1::SRSASN_CODE ret = msg.pack(bref); + srsran_assert(ret == asn1::SRSASN_SUCCESS, "Failed to pack other SIBs"); + msgs.push_back(std::move(buf)); + } + } + + return msgs; } byte_buffer srsran::srs_du::make_asn1_meas_time_cfg_buffer(const du_cell_config& du_cfg) diff --git a/lib/du_manager/converters/f1ap_configuration_helpers.h b/lib/du_manager/converters/f1ap_configuration_helpers.h index 9e54999301..3ac86b981d 100644 --- a/lib/du_manager/converters/f1ap_configuration_helpers.h +++ b/lib/du_manager/converters/f1ap_configuration_helpers.h @@ -45,10 +45,10 @@ byte_buffer make_asn1_rrc_cell_sib1_buffer(const du_cell_config& du_cfg, std::st byte_buffer make_asn1_rrc_cell_sib19_buffer(const ntn_config& ntn_cfg, std::string* js_str = nullptr); -/// \brief Derive packed cell BCCH-DL-SCH message from DU cell configuration. +/// \brief Generate packed cell BCCH-DL-SCH messages (SIB1 + SI messages) from DU cell configuration. /// \param[in] du_cfg DU Cell Configuration. /// \return byte buffer with packed cell BCCH-DL-SCH message. -byte_buffer make_asn1_rrc_cell_bcch_dl_sch_msg(const du_cell_config& du_cfg); +std::vector make_asn1_rrc_cell_bcch_dl_sch_msgs(const du_cell_config& du_cfg); /// \brief Derive packed cell measurementTimingConfiguration from DU cell configuration. /// \param[in] du_cfg DU Cell Configuration. diff --git a/lib/du_manager/converters/mac_config_helpers.cpp b/lib/du_manager/converters/mac_config_helpers.cpp index d8ee0954b5..27732aec40 100644 --- a/lib/du_manager/converters/mac_config_helpers.cpp +++ b/lib/du_manager/converters/mac_config_helpers.cpp @@ -26,21 +26,21 @@ using namespace srsran; /// Derives MAC Cell Configuration from DU Cell Configuration. -mac_cell_creation_request srsran::make_mac_cell_config(du_cell_index_t cell_index, - const du_cell_config& du_cfg, - byte_buffer bcch_dl_sch_payload, +mac_cell_creation_request srsran::make_mac_cell_config(du_cell_index_t cell_index, + const du_cell_config& du_cfg, + std::vector bcch_dl_sch_payloads, const sched_cell_configuration_request_message& sched_cell_cfg) { mac_cell_creation_request mac_cfg{}; - mac_cfg.cell_index = cell_index; - mac_cfg.pci = du_cfg.pci; - mac_cfg.scs_common = du_cfg.scs_common; - mac_cfg.ssb_cfg = du_cfg.ssb_cfg; - mac_cfg.dl_carrier = du_cfg.dl_carrier; - mac_cfg.ul_carrier = du_cfg.ul_carrier; - mac_cfg.cell_barred = du_cfg.cell_barred; - mac_cfg.intra_freq_resel = du_cfg.intra_freq_resel; - mac_cfg.bcch_dl_sch_payload = std::move(bcch_dl_sch_payload); - mac_cfg.sched_req = sched_cell_cfg; + mac_cfg.cell_index = cell_index; + mac_cfg.pci = du_cfg.pci; + mac_cfg.scs_common = du_cfg.scs_common; + mac_cfg.ssb_cfg = du_cfg.ssb_cfg; + mac_cfg.dl_carrier = du_cfg.dl_carrier; + mac_cfg.ul_carrier = du_cfg.ul_carrier; + mac_cfg.cell_barred = du_cfg.cell_barred; + mac_cfg.intra_freq_resel = du_cfg.intra_freq_resel; + mac_cfg.bcch_dl_sch_payloads = std::move(bcch_dl_sch_payloads); + mac_cfg.sched_req = sched_cell_cfg; return mac_cfg; } diff --git a/lib/du_manager/converters/mac_config_helpers.h b/lib/du_manager/converters/mac_config_helpers.h index 2e0fad9f3d..d02f94c903 100644 --- a/lib/du_manager/converters/mac_config_helpers.h +++ b/lib/du_manager/converters/mac_config_helpers.h @@ -31,7 +31,7 @@ struct du_cell_config; /// Derives MAC Cell Configuration from DU Cell Configuration. mac_cell_creation_request make_mac_cell_config(du_cell_index_t cell_index, const du_cell_config& du_cfg, - byte_buffer bcch_dl_sch_payload, + std::vector bcch_dl_sch_payloads, const sched_cell_configuration_request_message& sched_cell_cfg); } // namespace srsran diff --git a/lib/du_manager/converters/rlc_config_helpers.cpp b/lib/du_manager/converters/rlc_config_helpers.cpp index bd460949ba..5dd97305f9 100644 --- a/lib/du_manager/converters/rlc_config_helpers.cpp +++ b/lib/du_manager/converters/rlc_config_helpers.cpp @@ -27,7 +27,7 @@ using namespace srs_du; rlc_config srsran::srs_du::make_default_srb_rlc_config() { - rlc_config cfg; + rlc_config cfg = {}; cfg.mode = rlc_mode::am; cfg.am.tx.sn_field_length = rlc_am_sn_size::size12bits; cfg.am.tx.t_poll_retx = 45; @@ -57,7 +57,7 @@ static void fill_rlc_entity_creation_message_common(rlc_entity_creation_message& msg.tx_lower_dn = &bearer.connector.rlc_tx_buffer_state_notif; msg.timers = &du_services.timers; msg.pcell_executor = &du_services.cell_execs.executor(pcell_index); - msg.ue_executor = &du_services.ue_execs.executor(ue_index); + msg.ue_executor = &du_services.ue_execs.ctrl_executor(ue_index); } // for SRBs diff --git a/lib/du_manager/converters/scheduler_configuration_helpers.cpp b/lib/du_manager/converters/scheduler_configuration_helpers.cpp index 714dd68d8c..cc557d57b4 100644 --- a/lib/du_manager/converters/scheduler_configuration_helpers.cpp +++ b/lib/du_manager/converters/scheduler_configuration_helpers.cpp @@ -30,10 +30,16 @@ using namespace srsran; using namespace srs_du; /// Derives Scheduler Cell Configuration from DU Cell Configuration. -sched_cell_configuration_request_message srsran::srs_du::make_sched_cell_config_req(du_cell_index_t cell_index, - const du_cell_config& du_cfg, - unsigned sib1_payload_size) +sched_cell_configuration_request_message +srsran::srs_du::make_sched_cell_config_req(du_cell_index_t cell_index, + const du_cell_config& du_cfg, + span si_payload_sizes) { + srsran_assert(si_payload_sizes.size() >= 1, "SIB1 payload size needs to be set"); + srsran_assert(si_payload_sizes.size() - 1 == + (du_cfg.si_config.has_value() ? du_cfg.si_config->si_sched_info.size() : 0), + "Number of SI messages does not match the number of SI payload sizes"); + sched_cell_configuration_request_message sched_req{}; sched_req.cell_index = cell_index; sched_req.cell_group_index = (du_cell_group_index_t)0; @@ -49,10 +55,21 @@ sched_cell_configuration_request_message srsran::srs_du::make_sched_cell_config_ sched_req.nof_beams = 1; - /// SIB1 parameters. - sched_req.coreset0 = du_cfg.coreset0_idx; - sched_req.searchspace0 = du_cfg.searchspace0_idx; - sched_req.sib1_payload_size = sib1_payload_size; + sched_req.coreset0 = du_cfg.coreset0_idx; + sched_req.searchspace0 = du_cfg.searchspace0_idx; + + // Convert SIB1 and SI message info scheduling config. + sched_req.sib1_payload_size = si_payload_sizes[0].value(); + if (du_cfg.si_config.has_value()) { + sched_req.si_scheduling.emplace(); + sched_req.si_scheduling->si_window_len_slots = du_cfg.si_config->si_window_len_slots; + sched_req.si_scheduling->si_messages.resize(du_cfg.si_config->si_sched_info.size()); + for (unsigned i = 0; i != du_cfg.si_config->si_sched_info.size(); ++i) { + sched_req.si_scheduling->si_messages[i].period_radio_frames = + du_cfg.si_config->si_sched_info[i].si_period_radio_frames; + sched_req.si_scheduling->si_messages[i].msg_len = si_payload_sizes[i + 1]; + } + } sched_req.pucch_guardbands = config_helpers::build_pucch_guardbands_list( du_cfg.pucch_cfg, du_cfg.ul_cfg_common.init_ul_bwp.generic_params.crbs.length()); diff --git a/lib/du_manager/converters/scheduler_configuration_helpers.h b/lib/du_manager/converters/scheduler_configuration_helpers.h index e61f7484ce..035ed25a0d 100644 --- a/lib/du_manager/converters/scheduler_configuration_helpers.h +++ b/lib/du_manager/converters/scheduler_configuration_helpers.h @@ -33,8 +33,9 @@ namespace srs_du { struct du_ue; /// Derives Scheduler Cell Configuration from DU Cell Configuration. -sched_cell_configuration_request_message -make_sched_cell_config_req(du_cell_index_t cell_index, const du_cell_config& du_cfg, unsigned sib1_payload_size); +sched_cell_configuration_request_message make_sched_cell_config_req(du_cell_index_t cell_index, + const du_cell_config& du_cfg, + span si_payload_sizes); // Create scheduler UE Configuration Request based on DU UE configuration context. sched_ue_config_request create_scheduler_ue_config_request(const du_ue& u); diff --git a/lib/du_manager/du_manager_impl.h b/lib/du_manager/du_manager_impl.h index 3d62550f00..ef2834c4d7 100644 --- a/lib/du_manager/du_manager_impl.h +++ b/lib/du_manager/du_manager_impl.h @@ -81,7 +81,7 @@ class du_manager_impl final : public du_manager_interface bool running{false}; // Handler for DU tasks. - async_task_sequencer main_ctrl_loop; + fifo_async_task_scheduler main_ctrl_loop; }; } // namespace srs_du diff --git a/lib/du_manager/du_ue/du_bearer.cpp b/lib/du_manager/du_ue/du_bearer.cpp index ee50c3d80d..9797fcdc80 100644 --- a/lib/du_manager/du_ue/du_bearer.cpp +++ b/lib/du_manager/du_ue/du_bearer.cpp @@ -158,7 +158,7 @@ std::unique_ptr srsran::srs_du::create_drb(du_ue_index_t drb->dluptnl_info_list[0], drb->uluptnl_info_list[0], drb->connector.f1u_rx_sdu_notif, - timer_factory{du_params.services.timers, du_params.services.ue_execs.executor(ue_index)}); + timer_factory{du_params.services.timers, du_params.services.ue_execs.ctrl_executor(ue_index)}); if (f1u_drb == nullptr) { srslog::fetch_basic_logger("DU-MNG").warning("ue={}: Failed to connect F1-U bearer to CU-UP.", ue_index); return nullptr; diff --git a/lib/du_manager/du_ue/du_ue.h b/lib/du_manager/du_ue/du_ue.h index 77bedb4d23..697e93362d 100644 --- a/lib/du_manager/du_ue/du_ue.h +++ b/lib/du_manager/du_ue/du_ue.h @@ -64,6 +64,7 @@ struct du_ue { du_ue_bearer_manager bearers; ue_ran_resource_configurator resources; + unique_timer rlf_timer; bool reestablishment_pending = false; }; diff --git a/lib/du_manager/du_ue/du_ue_manager.cpp b/lib/du_manager/du_ue/du_ue_manager.cpp index 893405db60..b7a6cf09bd 100644 --- a/lib/du_manager/du_ue/du_ue_manager.cpp +++ b/lib/du_manager/du_ue/du_ue_manager.cpp @@ -143,7 +143,7 @@ async_task du_ue_manager::stop() // Disconnect notifiers of all UEs bearers from within the ue_executors context. for (ue_it = ue_db.begin(); ue_it != ue_db.end(); ++ue_it) { - CORO_AWAIT_VALUE(bool res, execute_on(cfg.services.ue_execs.executor((*ue_it)->ue_index))); + CORO_AWAIT_VALUE(bool res, execute_on(cfg.services.ue_execs.ctrl_executor((*ue_it)->ue_index))); if (not res) { CORO_EARLY_RETURN(); } @@ -263,7 +263,7 @@ void du_ue_manager::update_crnti(du_ue_index_t ue_index, rnti_t crnti) ue_db[ue_index]->rnti = crnti; } -void du_ue_manager::handle_radio_link_failure(du_ue_index_t ue_index, rlf_cause cause) +void du_ue_manager::handle_rlf_ue_release(du_ue_index_t ue_index, rlf_cause cause) { if (not ue_db.contains(ue_index)) { logger.warning("ue={}: Discarding RLF detection event. Cause: UE not found", ue_index); diff --git a/lib/du_manager/du_ue/du_ue_manager.h b/lib/du_manager/du_ue/du_ue_manager.h index d5002c6429..556fff0c14 100644 --- a/lib/du_manager/du_ue/du_ue_manager.h +++ b/lib/du_manager/du_ue/du_ue_manager.h @@ -27,7 +27,7 @@ #include "srsran/adt/slotted_array.h" #include "srsran/du_manager/du_manager.h" #include "srsran/du_manager/du_manager_params.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include namespace srsran { @@ -77,7 +77,7 @@ class du_ue_manager : public du_ue_manager_repository du_ue* find_rnti(rnti_t rnti) override; du_ue* find_f1ap_ue_id(gnb_du_ue_f1ap_id_t f1ap_ue_id) override; void remove_ue(du_ue_index_t ue_index) override; - void handle_radio_link_failure(du_ue_index_t ue_index, rlf_cause cause) override; + void handle_rlf_ue_release(du_ue_index_t ue_index, rlf_cause cause) override; du_manager_params& cfg; du_ran_resource_manager& cell_res_alloc; @@ -91,7 +91,7 @@ class du_ue_manager : public du_ue_manager_repository std::unordered_map rnti_to_ue_index; // task event loops indexed by ue_index - slotted_array ue_ctrl_loop; + slotted_array ue_ctrl_loop; }; } // namespace srs_du diff --git a/lib/du_manager/du_ue/du_ue_manager_repository.h b/lib/du_manager/du_ue/du_ue_manager_repository.h index 1bf9ab93a8..121a8cf824 100644 --- a/lib/du_manager/du_ue/du_ue_manager_repository.h +++ b/lib/du_manager/du_ue/du_ue_manager_repository.h @@ -52,8 +52,8 @@ class du_ue_manager_repository /// \brief Find UE context based on GNB-DU-UE-F1AP-ID. virtual du_ue* find_f1ap_ue_id(gnb_du_ue_f1ap_id_t f1ap_ue_id) = 0; - /// \brief Handle detected Radio Link Failures. - virtual void handle_radio_link_failure(du_ue_index_t ue_index, rlf_cause cause) = 0; + /// \brief Handle UE release due to detected Radio Link Failures. + virtual void handle_rlf_ue_release(du_ue_index_t ue_index, rlf_cause cause) = 0; /// \brief Access to the TEID pool that can be used to allocate/deallocate unique TEIDs for F1-U bearers. virtual gtpu_teid_pool& get_f1u_teid_pool() = 0; diff --git a/lib/du_manager/procedures/initial_du_setup_procedure.cpp b/lib/du_manager/procedures/initial_du_setup_procedure.cpp index 8e0d9790c5..b0090ee0f1 100644 --- a/lib/du_manager/procedures/initial_du_setup_procedure.cpp +++ b/lib/du_manager/procedures/initial_du_setup_procedure.cpp @@ -52,16 +52,20 @@ void initial_du_setup_procedure::operator()(coro_context>& ctx) // Configure DU Cells. for (unsigned idx = 0; idx < cell_mng.nof_cells(); ++idx) { - du_cell_index_t cell_index = to_du_cell_index(idx); - const du_cell_config& du_cfg = cell_mng.get_cell_cfg(cell_index); - byte_buffer sib1_payload = srs_du::make_asn1_rrc_cell_bcch_dl_sch_msg(du_cfg); - auto sched_cfg = srs_du::make_sched_cell_config_req(cell_index, du_cfg, sib1_payload.length()); + du_cell_index_t cell_index = to_du_cell_index(idx); + const du_cell_config& du_cfg = cell_mng.get_cell_cfg(cell_index); + std::vector bcch_msgs = srs_du::make_asn1_rrc_cell_bcch_dl_sch_msgs(du_cfg); + std::vector bcch_msg_payload_lens(bcch_msgs.size()); + for (unsigned i = 0; i < bcch_msgs.size(); ++i) { + bcch_msg_payload_lens[i] = units::bytes(bcch_msgs[i].length()); + } + auto sched_cfg = srs_du::make_sched_cell_config_req(cell_index, du_cfg, bcch_msg_payload_lens); error_type result = config_validators::validate_sched_cell_configuration_request_message(sched_cfg, params.mac.sched_cfg); if (result.is_error()) { report_fatal_error("Invalid cell={} configuration. Cause: {}", cell_index, result.error()); } - params.mac.cell_mng.add_cell(make_mac_cell_config(cell_index, du_cfg, std::move(sib1_payload), sched_cfg)); + params.mac.cell_mng.add_cell(make_mac_cell_config(cell_index, du_cfg, std::move(bcch_msgs), sched_cfg)); } // Activate DU Cells. diff --git a/lib/du_manager/procedures/ue_creation_procedure.cpp b/lib/du_manager/procedures/ue_creation_procedure.cpp index 42d4fb58b0..e9e74a5cb7 100644 --- a/lib/du_manager/procedures/ue_creation_procedure.cpp +++ b/lib/du_manager/procedures/ue_creation_procedure.cpp @@ -37,38 +37,79 @@ class du_ue_rlf_notification_adapter final : public du_ue_rlf_handler { public: du_ue_rlf_notification_adapter(du_ue_index_t ue_index_, - task_executor& ctrl_exec_, + std::chrono::milliseconds release_timeout_, + unique_timer release_request_timer, du_ue_manager_repository& du_ue_mng_) : - ue_index(ue_index_), ctrl_exec(ctrl_exec_), du_ue_mng(du_ue_mng_) + ue_index(ue_index_), + release_timeout(release_timeout_), + rel_timer(std::move(release_request_timer)), + du_ue_mng(du_ue_mng_), + logger(srslog::fetch_basic_logger("DU-MNG")) { } // Called by DU manager to disconnect the adapter during UE removal. - void disconnect() override { ue_index = INVALID_DU_UE_INDEX; } + void disconnect() override + { + std::lock_guard lock(timer_mutex); + rel_timer.reset(); + } + + void on_rlf_detected() override { start_rlf_timer(rlf_cause::max_mac_kos_reached); } + void on_crnti_ce_received() override + { + bool timer_was_running = false; + { + std::lock_guard lock(timer_mutex); + timer_was_running = rel_timer.is_running(); + rel_timer.stop(); + } + if (timer_was_running) { + logger.info("ue={}: RLF timer reset. Cause: C-RNTI CE was received for the UE", ue_index); + } + } - SRSRAN_NODISCARD bool on_rlf_detected() override { return handle_rlf_failure(rlf_cause::max_mac_kos_reached); } - void on_protocol_failure() override { handle_rlf_failure(rlf_cause::rlc_protocol_failure); } - void on_max_retx() override { handle_rlf_failure(rlf_cause::max_rlc_retxs_reached); } + void on_protocol_failure() override { start_rlf_timer(rlf_cause::rlc_protocol_failure); } + void on_max_retx() override { start_rlf_timer(rlf_cause::max_rlc_retxs_reached); } private: - bool handle_rlf_failure(rlf_cause cause) + void start_rlf_timer(rlf_cause cause) { - if (not ctrl_exec.execute([this, cause]() { - if (ue_index != INVALID_DU_UE_INDEX) { - // If adapter has not been disconnected, handle RLF. - du_ue_mng.handle_radio_link_failure(ue_index, cause); - } - })) { - srslog::fetch_basic_logger("DU-MNG").warning( - "ue={}: Discarding Radio Link Failure message. Cause: DU manager task queue is full", ue_index); - return false; + bool rlf_timer_start = false; + { + std::lock_guard lock(timer_mutex); + if (is_connected_nolock() and not rel_timer.is_running()) { + rel_timer.set(release_timeout, [this, cause](timer_id_t tid) { trigger_ue_release(cause); }); + rel_timer.run(); + rlf_timer_start = true; + } + } + if (rlf_timer_start) { + logger.info("ue={}: RLF detected. Timer of {} msec to release UE started...", ue_index, release_timeout.count()); } - return true; } - du_ue_index_t ue_index; - task_executor& ctrl_exec; - du_ue_manager_repository& du_ue_mng; + void trigger_ue_release(rlf_cause cause) + { + // Trigger UE release in the upper layers. + du_ue_mng.handle_rlf_ue_release(ue_index, cause); + + // Release timer so no new RLF is triggered for the same UE, after is scheduled for release. + disconnect(); + + logger.info("ue={}: RLF timer expired. Requesting a UE release...", ue_index); + } + + bool is_connected_nolock() const { return rel_timer.is_valid(); } + + const du_ue_index_t ue_index; + const std::chrono::milliseconds release_timeout; + unique_timer rel_timer; + du_ue_manager_repository& du_ue_mng; + srslog::basic_logger& logger; + + // This class is accessed directly from the MAC, so potential race conditions apply when accessing the \c rel_timer. + std::mutex timer_mutex; }; } // namespace @@ -95,13 +136,13 @@ void ue_creation_procedure::operator()(coro_context>& ctx) ue_ctx = create_du_ue_context(); if (ue_ctx == nullptr) { proc_logger.log_proc_failure("UE context not created because the RNTI is duplicated"); - clear_ue(); + CORO_AWAIT(clear_ue()); CORO_EARLY_RETURN(); } // > Initialize bearers and PHY/MAC PCell resources of the DU UE. if (not setup_du_ue_resources()) { - clear_ue(); + CORO_AWAIT(clear_ue()); CORO_EARLY_RETURN(); } @@ -109,7 +150,7 @@ void ue_creation_procedure::operator()(coro_context>& ctx) f1ap_resp = create_f1ap_ue(); if (not f1ap_resp.result) { proc_logger.log_proc_failure("Failure to create F1AP UE context"); - clear_ue(); + CORO_AWAIT(clear_ue()); CORO_EARLY_RETURN(); } @@ -123,7 +164,7 @@ void ue_creation_procedure::operator()(coro_context>& ctx) CORO_AWAIT_VALUE(mac_resp, create_mac_ue()); if (mac_resp.allocated_crnti == INVALID_RNTI) { proc_logger.log_proc_failure("Failure to create MAC UE context"); - clear_ue(); + CORO_AWAIT(clear_ue()); CORO_EARLY_RETURN(); } @@ -132,7 +173,11 @@ void ue_creation_procedure::operator()(coro_context>& ctx) // > Start Initial UL RRC Message Transfer by signalling MAC to notify CCCH to upper layers. if (not req.ul_ccch_msg.empty()) { - du_params.mac.ue_cfg.handle_ul_ccch_msg(ue_ctx->ue_index, req.ul_ccch_msg.copy()); + if (not du_params.mac.ue_cfg.handle_ul_ccch_msg(ue_ctx->ue_index, req.ul_ccch_msg.copy())) { + proc_logger.log_proc_failure("Failure to notify CCCH message to upper layers"); + CORO_AWAIT(clear_ue()); + CORO_EARLY_RETURN(); + } } proc_logger.log_proc_completed(); @@ -148,24 +193,42 @@ du_ue* ue_creation_procedure::create_du_ue_context() } // Create the adapter used by the MAC and RLC to notify the DU manager of a Radio Link Failure. - auto rlf_notifier = - std::make_unique(req.ue_index, du_params.services.du_mng_exec, ue_mng); + const du_cell_config& pcell_cfg = du_params.ran.cells[req.pcell_index]; + // Note: Between an RLF being detected and the UE being released, the DU manager will wait for enough time to allow + // the UE to perform C-RNTI CE (t310) and RRC re-establishment (t311). + std::chrono::milliseconds release_timeout = + pcell_cfg.ue_timers_and_constants.t310 + pcell_cfg.ue_timers_and_constants.t311; + auto rlf_notifier = std::make_unique( + req.ue_index, + release_timeout, + du_params.services.timers.create_unique_timer(du_params.services.du_mng_exec), + ue_mng); // Create the DU UE context. return ue_mng.add_ue( std::make_unique(req.ue_index, req.pcell_index, req.tc_rnti, std::move(rlf_notifier), std::move(ue_res))); } -void ue_creation_procedure::clear_ue() +async_task ue_creation_procedure::clear_ue() { - if (f1ap_resp.result) { - // TODO: Remove UE from F1AP. - } + return launch_async([this](coro_context>& ctx) { + CORO_BEGIN(ctx); + if (f1ap_resp.result) { + du_params.f1ap.ue_mng.handle_ue_deletion_request(req.ue_index); + } - if (ue_ctx != nullptr) { - // Clear UE from DU Manager UE repository. - ue_mng.remove_ue(ue_ctx->ue_index); - } + if (mac_resp.allocated_crnti != INVALID_RNTI) { + CORO_AWAIT(du_params.mac.ue_cfg.handle_ue_delete_request( + mac_ue_delete_request{req.pcell_index, req.ue_index, mac_resp.allocated_crnti})); + } + + if (ue_ctx != nullptr) { + // Clear UE from DU Manager UE repository. + ue_mng.remove_ue(ue_ctx->ue_index); + } + + CORO_RETURN(); + }); } bool ue_creation_procedure::setup_du_ue_resources() diff --git a/lib/du_manager/procedures/ue_creation_procedure.h b/lib/du_manager/procedures/ue_creation_procedure.h index 8b6072f762..c4439d3af8 100644 --- a/lib/du_manager/procedures/ue_creation_procedure.h +++ b/lib/du_manager/procedures/ue_creation_procedure.h @@ -75,7 +75,7 @@ class ue_creation_procedure du_ue* create_du_ue_context(); /// Remove UE from DU Manager UE repository. - void clear_ue(); + async_task clear_ue(); /// Setups DU manager resources used by DU UE being created. bool setup_du_ue_resources(); diff --git a/lib/du_manager/procedures/ue_deletion_procedure.cpp b/lib/du_manager/procedures/ue_deletion_procedure.cpp index 91e10f694d..fc80bcef9b 100644 --- a/lib/du_manager/procedures/ue_deletion_procedure.cpp +++ b/lib/du_manager/procedures/ue_deletion_procedure.cpp @@ -87,7 +87,7 @@ async_task ue_deletion_procedure::disconnect_inter_layer_interfaces() ue->rlf_notifier->disconnect(); return dispatch_and_resume_on( - du_params.services.ue_execs.executor(msg.ue_index), du_params.services.du_mng_exec, [this]() { + du_params.services.ue_execs.ctrl_executor(msg.ue_index), du_params.services.du_mng_exec, [this]() { // > Disconnect DRBs. for (auto& drb_pair : ue->bearers.drbs()) { du_ue_drb& drb = *drb_pair.second; diff --git a/lib/e1ap/cu_cp/e1ap_cu_cp_impl.cpp b/lib/e1ap/cu_cp/e1ap_cu_cp_impl.cpp index 0f35c63ac4..6c58927069 100644 --- a/lib/e1ap/cu_cp/e1ap_cu_cp_impl.cpp +++ b/lib/e1ap/cu_cp/e1ap_cu_cp_impl.cpp @@ -42,7 +42,7 @@ e1ap_cu_cp_impl::e1ap_cu_cp_impl(e1ap_message_notifier& e1ap_pdu_notifie cu_cp_notifier(cu_cp_notifier_), ctrl_exec(ctrl_exec_), timers(timer_factory{timers_, ctrl_exec_}), - ue_ctxt_list(timers), + ue_ctxt_list(timers, logger), ev_mng(timers) { } @@ -220,7 +220,7 @@ void e1ap_cu_cp_impl::handle_message(const e1ap_message& msg) break; } })) { - logger.warning("Discarding E1AP PDU. Cause: CU-CP task queue is full."); + logger.warning("Discarding E1AP PDU. Cause: CU-CP task queue is full"); } } @@ -326,8 +326,7 @@ void e1ap_cu_cp_impl::handle_successful_outcome(const asn1::e1ap::successful_out // Set transaction result and resume suspended procedure. if (not ev_mng.transactions.set_response(transaction_id.value(), outcome)) { - logger.warning("Ignoring message. Cause: Transaction with id={} has already completed.", - transaction_id.value()); + logger.warning("Ignoring message. Cause: Transaction with id={} has already completed", transaction_id.value()); } } } @@ -353,8 +352,7 @@ void e1ap_cu_cp_impl::handle_unsuccessful_outcome(const asn1::e1ap::unsuccessful // Set transaction result and resume suspended procedure. if (not ev_mng.transactions.set_response(transaction_id.value(), outcome)) { - logger.warning("Ignoring message. Cause: Transaction with id={} has already completed.", - transaction_id.value()); + logger.warning("Ignoring message. Cause: Transaction with id={} has already completed", transaction_id.value()); } } } @@ -362,11 +360,21 @@ void e1ap_cu_cp_impl::handle_unsuccessful_outcome(const asn1::e1ap::unsuccessful void e1ap_cu_cp_impl::update_ue_context(ue_index_t ue_index, ue_index_t old_ue_index) { if (!ue_ctxt_list.contains(old_ue_index)) { - logger.debug("Ue context for ue={} not found.", old_ue_index); + logger.debug("ue={}: UE context not found", old_ue_index); return; } logger.debug("Updating UE Context from ue_index={} to ue_index={}", old_ue_index, ue_index); ue_ctxt_list.update_ue_index(ue_index, old_ue_index); +} + +void e1ap_cu_cp_impl::remove_bearer_context(ue_index_t ue_index) +{ + if (!ue_ctxt_list.contains(ue_index)) { + logger.debug("ue={}: UE context not found", ue_index); + return; + } + + ue_ctxt_list.remove_ue(ue_index); } \ No newline at end of file diff --git a/lib/e1ap/cu_cp/e1ap_cu_cp_impl.h b/lib/e1ap/cu_cp/e1ap_cu_cp_impl.h index e17e19628a..0173c9b6a7 100644 --- a/lib/e1ap/cu_cp/e1ap_cu_cp_impl.h +++ b/lib/e1ap/cu_cp/e1ap_cu_cp_impl.h @@ -48,23 +48,29 @@ class e1ap_cu_cp_impl final : public e1ap_interface task_executor& ctrl_exec_); ~e1ap_cu_cp_impl(); - // e1ap connection manager functions + // e1ap_connection_manager functions void handle_cu_up_e1_setup_response(const cu_up_e1_setup_response& msg) override; - // e1ap bearer context manager functions + // e1ap_bearer_context_manager functions async_task handle_bearer_context_setup_request(const e1ap_bearer_context_setup_request& msg) override; async_task handle_bearer_context_modification_request(const e1ap_bearer_context_modification_request& request) override; async_task handle_bearer_context_release_command(const e1ap_bearer_context_release_command& command) override; - // e1ap message handler functions + // e1ap_message_handler functions void handle_message(const e1ap_message& msg) override; void handle_connection_loss() override {} - // e1ap ue handler functions + // e1ap_ue_handler functions void update_ue_context(ue_index_t ue_index, ue_index_t old_ue_index) override; + // e1ap_bearer_context_removal_handler functions + void remove_bearer_context(ue_index_t ue_index) override; + + // e1ap_statistics_handler functions + size_t get_nof_ues() const override { return ue_ctxt_list.size(); } + private: /// \brief Notify about the reception of an initiating message. /// \param[in] msg The received initiating message. diff --git a/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.cpp b/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.cpp index 4da9191a8e..76c6660bf7 100644 --- a/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.cpp +++ b/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.cpp @@ -78,9 +78,6 @@ void bearer_context_release_procedure::handle_bearer_context_release_complete() resp.to_json(js); logger.debug("Containerized BearerContextReleaseComplete: {}", js.to_string()); } - if (command.pdu.init_msg().value.bearer_context_release_cmd()->gnb_cu_cp_ue_e1ap_id == resp->gnb_cu_cp_ue_e1ap_id) { - ue_ctxt_list.remove_ue(ue_index); - } logger.debug("ue={}: \"{}\" finalized.", ue_index, name()); diff --git a/lib/e1ap/cu_cp/ue_context/e1ap_cu_cp_ue_context.h b/lib/e1ap/cu_cp/ue_context/e1ap_cu_cp_ue_context.h index 5ed1e0a7ca..727d44b09f 100644 --- a/lib/e1ap/cu_cp/ue_context/e1ap_cu_cp_ue_context.h +++ b/lib/e1ap/cu_cp/ue_context/e1ap_cu_cp_ue_context.h @@ -49,7 +49,7 @@ struct e1ap_ue_context { class e1ap_ue_context_list { public: - e1ap_ue_context_list(timer_factory timers_) : timers(timers_) {} + e1ap_ue_context_list(timer_factory timers_, srslog::basic_logger& logger_) : timers(timers_), logger(logger_) {} bool contains(gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id) const { return ues.find(cu_cp_ue_e1ap_id) != ues.end(); } @@ -63,17 +63,35 @@ class e1ap_ue_context_list } if (ues.find(ue_index_to_ue_e1ap_id.at(ue_index)) == ues.end()) { srslog::fetch_basic_logger("CU-CP-E1") - .warning("No UE context found for cu_cp_ue_e1ap_id={}.", ue_index_to_ue_e1ap_id.at(ue_index)); + .warning("No UE context found for cu_cp_ue_e1ap_id={}", ue_index_to_ue_e1ap_id.at(ue_index)); return false; } return true; } - e1ap_ue_context& operator[](gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id) { return ues.at(cu_cp_ue_e1ap_id); } - e1ap_ue_context& operator[](ue_index_t ue_index) { return ues.at(ue_index_to_ue_e1ap_id.at(ue_index)); } + e1ap_ue_context& operator[](gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id) + { + srsran_assert( + ues.find(cu_cp_ue_e1ap_id) != ues.end(), "cu_cp_ue_e1ap_id={}: E1AP UE context not found", cu_cp_ue_e1ap_id); + return ues.at(cu_cp_ue_e1ap_id); + } + e1ap_ue_context& operator[](ue_index_t ue_index) + { + srsran_assert(ue_index_to_ue_e1ap_id.find(ue_index) != ue_index_to_ue_e1ap_id.end(), + "ue={} gNB-CU-CP-UE-E1AP-ID not found", + ue_index); + srsran_assert(ues.find(ue_index_to_ue_e1ap_id.at(ue_index)) != ues.end(), + "cu_cp_ue_e1ap_id={}: E1AP UE context not found", + ue_index_to_ue_e1ap_id.at(ue_index)); + return ues.at(ue_index_to_ue_e1ap_id.at(ue_index)); + } e1ap_ue_context& add_ue(ue_index_t ue_index, gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id) { + srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); + srsran_assert(cu_cp_ue_e1ap_id != gnb_cu_cp_ue_e1ap_id_t::invalid, "Invalid cu_cp_ue_e1ap_id={}", cu_cp_ue_e1ap_id); + + logger.debug("ue={} cu_cp_ue_e1ap_id={}: Adding E1AP UE context", ue_index, cu_cp_ue_e1ap_id); ues.emplace(std::piecewise_construct, std::forward_as_tuple(cu_cp_ue_e1ap_id), std::forward_as_tuple(ue_index, cu_cp_ue_e1ap_id, timers)); @@ -81,33 +99,25 @@ class e1ap_ue_context_list return ues.at(cu_cp_ue_e1ap_id); } - void remove_ue(gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id) - { - srsran_assert( - ues.find(cu_cp_ue_e1ap_id) != ues.end(), "UE context for gnb_cu_cp_ue_e1ap_id={} not found.", cu_cp_ue_e1ap_id); - - ue_index_t ue_index = ues.at(cu_cp_ue_e1ap_id).ue_index; - - srsran_assert(ue_index_to_ue_e1ap_id.find(ue_index) != ue_index_to_ue_e1ap_id.end(), - "GNB-CU-CP-UE-E1AP-ID for ue_index={} not found.", - ue_index); - - ue_index_to_ue_e1ap_id.erase(ue_index); - ues.erase(cu_cp_ue_e1ap_id); - } - void remove_ue(ue_index_t ue_index) { - srsran_assert(ue_index_to_ue_e1ap_id.find(ue_index) != ue_index_to_ue_e1ap_id.end(), - "GNB-CU-CP-UE-E1AP-ID for ue_index={} not found.", - ue_index); + srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); + + if (ue_index_to_ue_e1ap_id.find(ue_index) == ue_index_to_ue_e1ap_id.end()) { + logger.warning("ue={}: GNB-CU-CP-UE-E1AP-ID not found", ue_index); + return; + } + // Remove UE from lookup gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = ue_index_to_ue_e1ap_id.at(ue_index); + ue_index_to_ue_e1ap_id.erase(ue_index); - srsran_assert( - ues.find(cu_cp_ue_e1ap_id) != ues.end(), "UE context for gnb_cu_cp_ue_e1ap_id={} not found.", cu_cp_ue_e1ap_id); + if (ues.find(cu_cp_ue_e1ap_id) == ues.end()) { + logger.warning("cu_cp_ue_e1ap_id={}: UE context not found", cu_cp_ue_e1ap_id); + return; + } - ue_index_to_ue_e1ap_id.erase(ue_index); + logger.debug("ue={} cu_cp_ue_e1ap_id={}: Removing F1AP UE context", ue_index, cu_cp_ue_e1ap_id); ues.erase(cu_cp_ue_e1ap_id); } @@ -135,33 +145,42 @@ class e1ap_ue_context_list return gnb_cu_cp_ue_e1ap_id_t::invalid; } - void update_ue_index(ue_index_t ue_index, ue_index_t old_ue_index) + void update_ue_index(ue_index_t new_ue_index, ue_index_t old_ue_index) { + srsran_assert(new_ue_index != ue_index_t::invalid, "Invalid new_ue_index={}", new_ue_index); + srsran_assert(old_ue_index != ue_index_t::invalid, "Invalid old_ue_index={}", old_ue_index); + srsran_assert(ue_index_to_ue_e1ap_id.find(old_ue_index) != ue_index_to_ue_e1ap_id.end(), + "ue={}: GNB-CU-CP-UE-E1AP-ID not found", + old_ue_index); + // no need to update if the ue indexes are equal - if (ue_index == old_ue_index) { + if (new_ue_index == old_ue_index) { return; } - srsran_assert(ue_index_to_ue_e1ap_id.find(old_ue_index) != ue_index_to_ue_e1ap_id.end(), - "GNB-CU-CP-UE-E1AP-ID for ue_index={} not found.", - old_ue_index); - gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = ue_index_to_ue_e1ap_id.at(old_ue_index); srsran_assert( - ues.find(cu_cp_ue_e1ap_id) != ues.end(), "UE context for gnb_cu_cp_ue_e1ap_id={} not found.", cu_cp_ue_e1ap_id); + ues.find(cu_cp_ue_e1ap_id) != ues.end(), "cu_cp_ue_e1ap_id={}: UE context not found", cu_cp_ue_e1ap_id); - // update ue_index + // Update UE context auto& ue_ctxt = ues.at(cu_cp_ue_e1ap_id); - ue_ctxt.ue_index = ue_index; + ue_ctxt.ue_index = new_ue_index; - // update lookup - ue_index_to_ue_e1ap_id.emplace(ue_index, cu_cp_ue_e1ap_id); + // Update lookup + ue_index_to_ue_e1ap_id.emplace(new_ue_index, cu_cp_ue_e1ap_id); ue_index_to_ue_e1ap_id.erase(old_ue_index); + + logger.debug("cu_cp_ue_e1ap_id={} cu_up_ue_e1ap_id={}: Updated UE index from ue_index={} to ue_index={}", + cu_cp_ue_e1ap_id, + ues.at(cu_cp_ue_e1ap_id).cu_up_ue_e1ap_id, + old_ue_index, + new_ue_index); } private: - timer_factory timers; + timer_factory timers; + srslog::basic_logger& logger; std::unordered_map ues; // indexed by gnb_cu_cp_ue_e1ap_id std::unordered_map ue_index_to_ue_e1ap_id; // indexed by ue_index diff --git a/lib/e1ap/cu_up/e1ap_cu_up_impl.h b/lib/e1ap/cu_up/e1ap_cu_up_impl.h index 207d6cc1c7..cf871863a2 100644 --- a/lib/e1ap/cu_up/e1ap_cu_up_impl.h +++ b/lib/e1ap/cu_up/e1ap_cu_up_impl.h @@ -60,6 +60,9 @@ class e1ap_cu_up_impl final : public e1ap_interface // e1ap event handler functions void handle_connection_loss() override {} + // e1ap_statistics_handler functions + size_t get_nof_ues() const override { return ue_ctxt_list.size(); } + private: /// \brief Notify about the reception of an initiating message. /// \param[in] msg The received initiating message. diff --git a/lib/e2/common/e2_entity.h b/lib/e2/common/e2_entity.h index 03f9dcd2b8..e8217e60bf 100644 --- a/lib/e2/common/e2_entity.h +++ b/lib/e2/common/e2_entity.h @@ -33,7 +33,7 @@ #include "srsran/e2/e2sm/e2sm_manager.h" #include "srsran/f1ap/du/f1ap_du.h" #include "srsran/ran/nr_cgi.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include #include @@ -71,8 +71,8 @@ class e2_entity final : public e2_interface e2ap_configuration& cfg; // Handler for E2AP tasks. - task_executor& task_exec; - async_task_sequencer main_ctrl_loop; + task_executor& task_exec; + fifo_async_task_scheduler main_ctrl_loop; std::unique_ptr e2_pdu_notifier = nullptr; std::unique_ptr e2sm_mngr = nullptr; diff --git a/lib/e2/common/e2_impl.h b/lib/e2/common/e2_impl.h index 248293fbef..c69179f4db 100644 --- a/lib/e2/common/e2_impl.h +++ b/lib/e2/common/e2_impl.h @@ -32,7 +32,7 @@ #include "srsran/e2/e2sm/e2sm_factory.h" #include "srsran/e2/e2sm/e2sm_manager.h" #include "srsran/ran/nr_cgi.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include #include @@ -115,7 +115,7 @@ class e2_impl final : public e2_interface e2_subscription_setup_procedure subscribe_proc; e2_subscription_delete_procedure subscribe_delete_proc; std::unique_ptr events; - async_task_sequencer async_tasks; + fifo_async_task_scheduler async_tasks; unsigned current_transaction_id = 0; // store current E2AP transaction id }; diff --git a/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_asn1_packer.cpp b/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_asn1_packer.cpp index 2b4c8ef378..bc7b560672 100644 --- a/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_asn1_packer.cpp +++ b/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_asn1_packer.cpp @@ -65,7 +65,6 @@ e2sm_kpm_asn1_packer::handle_packed_event_trigger_definition(const srsran::byte_ asn1::unbounded_octstring e2sm_kpm_asn1_packer::pack_ran_function_description() { e2_sm_kpm_ra_nfunction_description_s ran_function_desc; - asn1::unbounded_octstring ran_function_description; // Add ran_function_name item. ran_function_desc.ran_function_name.ran_function_short_name.resize(short_name.size()); ran_function_desc.ran_function_name.ran_function_e2_sm_oid.resize(oid.size()); @@ -163,10 +162,10 @@ asn1::unbounded_octstring e2sm_kpm_asn1_packer::pack_ran_function_descript asn1::bit_ref bref(buf); if (ran_function_desc.pack(bref) != asn1::SRSASN_SUCCESS) { printf("Failed to pack E2SM KPM RAN Function Description\n"); - return ran_function_description; + asn1::unbounded_octstring err_buf; + return err_buf; } - ran_function_description.resize(buf.length()); - std::copy(buf.begin(), buf.end(), ran_function_description.begin()); + asn1::unbounded_octstring ran_function_description(buf.begin(), buf.end()); return ran_function_description; } \ No newline at end of file diff --git a/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_du_meas_provider_impl.cpp b/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_du_meas_provider_impl.cpp index 277af8b513..d6085029ed 100644 --- a/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_du_meas_provider_impl.cpp +++ b/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_du_meas_provider_impl.cpp @@ -29,31 +29,68 @@ using namespace srsran; e2sm_kpm_du_meas_provider_impl::e2sm_kpm_du_meas_provider_impl(srs_du::f1ap_ue_id_translator& f1ap_ue_id_translator_) : logger(srslog::fetch_basic_logger("E2SM-KPM")), f1ap_ue_id_provider(f1ap_ue_id_translator_) { - // Array of supported metrics in string format. - supported_metrics = {"CQI", - "RSRP", - "RSRQ", - "DRB.RlcPacketDropRateDl", - "DRB.RlcSduTransmittedVolumeDL", - "DRB.RlcSduTransmittedVolumeUL"}; -} + // Array of supported metrics. + supported_metrics.emplace( + "CQI", e2sm_kpm_supported_metric_t{NO_LABEL, ALL_LEVELS, false, &e2sm_kpm_du_meas_provider_impl::get_cqi}); + supported_metrics.emplace( + "RSRP", e2sm_kpm_supported_metric_t{NO_LABEL, ALL_LEVELS, false, &e2sm_kpm_du_meas_provider_impl::get_rsrp}); + supported_metrics.emplace( + "RSRQ", e2sm_kpm_supported_metric_t{NO_LABEL, ALL_LEVELS, false, &e2sm_kpm_du_meas_provider_impl::get_rsrq}); -bool e2sm_kpm_du_meas_provider_impl::check_measurement_family(const meas_type_c meas_type, const char* family_name) -{ - std::string family_name_str = family_name; - family_name_str += "."; - if (meas_type.meas_name().to_string().find(family_name_str) != std::string::npos) { - return true; - } - return false; + supported_metrics.emplace( + "DRB.PacketSuccessRateUlgNBUu", + e2sm_kpm_supported_metric_t{ + NO_LABEL, E2_NODE_LEVEL | UE_LEVEL, true, &e2sm_kpm_du_meas_provider_impl::get_drb_ul_success_rate}); + supported_metrics.emplace( + "DRB.UEThpUl", + e2sm_kpm_supported_metric_t{ + NO_LABEL, E2_NODE_LEVEL | UE_LEVEL, true, &e2sm_kpm_du_meas_provider_impl::get_drb_ul_mean_throughput}); + supported_metrics.emplace( + "DRB.RlcPacketDropRateDl", + e2sm_kpm_supported_metric_t{ + NO_LABEL, ALL_LEVELS, true, &e2sm_kpm_du_meas_provider_impl::get_drb_rlc_packet_drop_rate_dl}); + supported_metrics.emplace( + "DRB.RlcSduTransmittedVolumeDL", + e2sm_kpm_supported_metric_t{ + NO_LABEL, ALL_LEVELS, true, &e2sm_kpm_du_meas_provider_impl::get_drb_rlc_sdu_transmitted_volume_dl}); + supported_metrics.emplace( + "DRB.RlcSduTransmittedVolumeUL", + e2sm_kpm_supported_metric_t{ + NO_LABEL, ALL_LEVELS, true, &e2sm_kpm_du_meas_provider_impl::get_drb_rlc_sdu_transmitted_volume_ul}); + + // Check if the supported metrics are matching e2sm_kpm metrics definitions. + check_e2sm_kpm_metrics_definitions(get_e2sm_kpm_28_552_metrics()); + check_e2sm_kpm_metrics_definitions(get_e2sm_kpm_oran_metrics()); } -bool e2sm_kpm_du_meas_provider_impl::check_measurement_name(const meas_type_c meas_type, const char* meas) +bool e2sm_kpm_du_meas_provider_impl::check_e2sm_kpm_metrics_definitions(span metric_defs) { - if (strcmp(meas_type.meas_name().to_string().c_str(), meas) == 0) { - return true; + std::string metric_name; + auto name_matches = [&metric_name](const e2sm_kpm_metric_t& x) { + return (x.name == metric_name.c_str() or x.name == metric_name); + }; + + for (auto& supported_metric : supported_metrics) { + metric_name = supported_metric.first; + auto it = std::find_if(metric_defs.begin(), metric_defs.end(), name_matches); + if (it == metric_defs.end()) { + continue; + } + + if (supported_metric.second.supported_labels & ~(it->optional_labels | e2sm_kpm_label_enum::NO_LABEL)) { + logger.debug("Wrong definition of the supported metric: \"{}\", labels cannot be supported.", metric_name); + } + + if (supported_metric.second.supported_levels & ~it->optional_levels) { + logger.debug("Wrong definition of the supported metric: \"{}\", level cannot be supported.", metric_name); + } + + if (is_cell_id_required(*it) and not supported_metric.second.cell_scope_supported) { + logger.debug("Wrong definition of the supported metric: \"{}\", cell scope has to be supported.", + metric_name.c_str()); + } } - return false; + return true; } void e2sm_kpm_du_meas_provider_impl::report_metrics(span ue_metrics) @@ -66,28 +103,13 @@ void e2sm_kpm_du_meas_provider_impl::report_metrics(span= ue_aggr_rlc_metrics.size()) { - ue_aggr_rlc_metrics.resize(metrics.ue_index + 1); - } - - if (bearer_id == 1) { - // Reset aggregated RLC metrics when metrics for bearer_id = 0 are received. - ue_aggr_rlc_metrics[metrics.ue_index].rx.num_lost_pdus = metrics.rx.num_lost_pdus; - ue_aggr_rlc_metrics[metrics.ue_index].rx.num_malformed_pdus = metrics.rx.num_malformed_pdus; - ue_aggr_rlc_metrics[metrics.ue_index].rx.num_sdus = metrics.rx.num_sdus; - ue_aggr_rlc_metrics[metrics.ue_index].rx.num_sdu_bytes = metrics.rx.num_sdu_bytes; - ue_aggr_rlc_metrics[metrics.ue_index].rx.num_pdus = metrics.rx.num_pdus; - ue_aggr_rlc_metrics[metrics.ue_index].rx.num_pdu_bytes = metrics.rx.num_pdu_bytes; - ue_aggr_rlc_metrics[metrics.ue_index].tx.num_sdus = metrics.tx.num_sdus; - ue_aggr_rlc_metrics[metrics.ue_index].tx.num_sdu_bytes = metrics.tx.num_sdu_bytes; - ue_aggr_rlc_metrics[metrics.ue_index].tx.num_dropped_sdus = metrics.tx.num_dropped_sdus; - ue_aggr_rlc_metrics[metrics.ue_index].tx.num_discarded_sdus = metrics.tx.num_discarded_sdus; - ue_aggr_rlc_metrics[metrics.ue_index].tx.num_discard_failures = metrics.tx.num_discard_failures; - ue_aggr_rlc_metrics[metrics.ue_index].tx.num_pdus = metrics.tx.num_pdus; - ue_aggr_rlc_metrics[metrics.ue_index].tx.num_pdu_bytes = metrics.tx.num_pdu_bytes; + logger.debug("Received RLC metrics: ue={} {}.", metrics.ue_index, metrics.rb_id.get_drb_id()); + ue_aggr_rlc_metrics[metrics.ue_index].ue_index = metrics.ue_index; + if (metrics.rb_id.get_drb_id() == drb_id_t::drb1) { + // Reset aggregated RLC metrics when metrics for drb1 are received. + ue_aggr_rlc_metrics[metrics.ue_index].rx = metrics.rx; + ue_aggr_rlc_metrics[metrics.ue_index].tx = metrics.tx; + ue_aggr_rlc_metrics[metrics.ue_index].counter = 1; } else { // Otherwise, aggregate RLC metrics for each UE. ue_aggr_rlc_metrics[metrics.ue_index].rx.num_lost_pdus += metrics.rx.num_lost_pdus; @@ -103,12 +125,21 @@ void e2sm_kpm_du_meas_provider_impl::report_metrics(const rlc_metrics& metrics) ue_aggr_rlc_metrics[metrics.ue_index].tx.num_discard_failures += metrics.tx.num_discard_failures; ue_aggr_rlc_metrics[metrics.ue_index].tx.num_pdus += metrics.tx.num_pdus; ue_aggr_rlc_metrics[metrics.ue_index].tx.num_pdu_bytes += metrics.tx.num_pdu_bytes; + ue_aggr_rlc_metrics[metrics.ue_index].counter++; } } std::vector e2sm_kpm_du_meas_provider_impl::get_supported_metric_names(e2sm_kpm_metric_level_enum level) { - return supported_metrics; + std::vector metrics; + for (auto& m : supported_metrics) { + if ((level & E2_NODE_LEVEL) and (m.second.supported_levels & E2_NODE_LEVEL)) { + metrics.push_back(m.first); + } else if ((level & UE_LEVEL) and (m.second.supported_levels & UE_LEVEL)) { + metrics.push_back(m.first); + } + } + return metrics; } bool e2sm_kpm_du_meas_provider_impl::cell_supported(const asn1::e2sm_kpm::cgi_c& cell_global_id) @@ -135,12 +166,12 @@ bool e2sm_kpm_du_meas_provider_impl::metric_supported(const asn1::e2sm_kpm::meas const bool& cell_scope) { if (!label.no_label_present) { - logger.debug("Currently only NO_LABEL metric supported"); + logger.debug("Currently only NO_LABEL metric supported."); return false; } for (auto& metric : supported_metrics) { - if (strcmp(meas_type.meas_name().to_string().c_str(), metric.c_str()) == 0) { + if (strcmp(meas_type.meas_name().to_string().c_str(), metric.first.c_str()) == 0) { return true; } } @@ -169,80 +200,223 @@ bool e2sm_kpm_du_meas_provider_impl::get_meas_data(const asn1::e2sm_kpm::meas_ty const srsran::optional cell_global_id, std::vector& items) { - if (check_measurement_family(meas_type, "DRB")) { - return get_meas_data_drb_family(meas_type, label_info_list, ues, cell_global_id, items); - } - - if (last_ue_metrics.size() == 0) { - // TODO: handle this case correctly - meas_record_item_c meas_record_item; - meas_record_item.set_integer() = 0; - items.push_back(meas_record_item); + metric_meas_getter_func_ptr metric_meas_getter_func; + auto it = supported_metrics.find(meas_type.meas_name().to_string().c_str()); + if (it == supported_metrics.end()) { + logger.debug("Metric {} not supported.", meas_type.meas_name().to_string()); return false; } + metric_meas_getter_func = it->second.func; + srsran_assert(metric_meas_getter_func != nullptr, "Metric getter function cannot be empty."); + return (this->*metric_meas_getter_func)(label_info_list, ues, cell_global_id, items); +} - scheduler_ue_metrics ue_metrics = last_ue_metrics[0]; +bool e2sm_kpm_du_meas_provider_impl::get_cqi(const asn1::e2sm_kpm::label_info_list_l label_info_list, + const std::vector& ues, + const srsran::optional cell_global_id, + std::vector& items) +{ + bool meas_collected = false; + scheduler_ue_metrics ue_metrics = last_ue_metrics[0]; - if (cell_global_id.has_value()) { - // TODO: need to find the proper cell - } + meas_record_item_c meas_record_item; + meas_record_item.set_integer() = (int)ue_metrics.cqi; + items.push_back(meas_record_item); + meas_collected = true; - if (ues.size()) { - // TODO: need to get measurements for the listed UEs - } + return meas_collected; +} - if (label_info_list.size()) { - // TODO: need to get measurements with present labels - } +bool e2sm_kpm_du_meas_provider_impl::get_rsrp(const asn1::e2sm_kpm::label_info_list_l label_info_list, + const std::vector& ues, + const srsran::optional cell_global_id, + std::vector& items) +{ + bool meas_collected = false; + scheduler_ue_metrics ue_metrics = last_ue_metrics[0]; meas_record_item_c meas_record_item; - if (check_measurement_name(meas_type, "CQI")) { - meas_record_item.set_integer() = (int)ue_metrics.cqi; - } else if (check_measurement_name(meas_type, "RSRP")) { - meas_record_item.set_integer() = (int)ue_metrics.pusch_snr_db; - } else if (check_measurement_name(meas_type, "RSRQ")) { - meas_record_item.set_integer() = (int)ue_metrics.pusch_snr_db; - } + meas_record_item.set_integer() = (int)ue_metrics.pusch_snr_db; + items.push_back(meas_record_item); + meas_collected = true; + return meas_collected; +} + +bool e2sm_kpm_du_meas_provider_impl::get_rsrq(const asn1::e2sm_kpm::label_info_list_l label_info_list, + const std::vector& ues, + const srsran::optional cell_global_id, + std::vector& items) +{ + bool meas_collected = false; + scheduler_ue_metrics ue_metrics = last_ue_metrics[0]; + + meas_record_item_c meas_record_item; + meas_record_item.set_integer() = (int)ue_metrics.pusch_snr_db; items.push_back(meas_record_item); - return true; + meas_collected = true; + + return meas_collected; } -bool e2sm_kpm_du_meas_provider_impl::get_meas_data_drb_family( - const asn1::e2sm_kpm::meas_type_c& meas_type, +bool e2sm_kpm_du_meas_provider_impl::get_drb_ul_mean_throughput( const asn1::e2sm_kpm::label_info_list_l label_info_list, const std::vector& ues, const srsran::optional cell_global_id, std::vector& items) { bool meas_collected = false; - + if ((label_info_list.size() > 1 or + (label_info_list.size() == 1 and not label_info_list[0].meas_label.no_label_present))) { + logger.debug("Metric: DRB.UEThpUl supports only NO_LABEL label."); + return meas_collected; + } + unsigned seconds = 1; + std::map ue_throughput; + if (ue_aggr_rlc_metrics.size() == 0) { + return meas_collected; + } + for (auto& ue : ue_aggr_rlc_metrics) { + ue_throughput[ue.first] = (ue.second.rx.num_pdu_bytes / ue.second.counter) / seconds; + } if (ues.size() == 0) { - // TODO: support E2 level measurement (i.e., aggregated) + meas_record_item_c meas_record_item; + int total_throughput = 0; + for (auto& ue : ue_throughput) { + total_throughput += ue.second; + } + meas_record_item.set_integer() = total_throughput / ue_throughput.size(); + items.push_back(meas_record_item); + meas_collected = true; + } + + for (auto& ue : ues) { + meas_record_item_c meas_record_item; + gnb_cu_ue_f1ap_id_t gnb_cu_ue_f1ap_id = int_to_gnb_cu_ue_f1ap_id(ue.gnb_du_ueid().gnb_cu_ue_f1_ap_id); + uint32_t ue_idx = f1ap_ue_id_provider.get_ue_index(gnb_cu_ue_f1ap_id); + if (ue_throughput.count(ue_idx) == 0) { + meas_record_item.set_no_value(); + items.push_back(meas_record_item); + meas_collected = true; + continue; + } + meas_record_item.set_integer() = ue_throughput[ue_idx]; + items.push_back(meas_record_item); + meas_collected = true; } + return meas_collected; +} + +bool e2sm_kpm_du_meas_provider_impl::get_drb_ul_success_rate( + const asn1::e2sm_kpm::label_info_list_l label_info_list, + const std::vector& ues, + const srsran::optional cell_global_id, + std::vector& items) +{ + bool meas_collected = false; + if ((label_info_list.size() > 1 or + (label_info_list.size() == 1 and not label_info_list[0].meas_label.no_label_present))) { + logger.debug("Metric: DRB.PacketSuccessRateUlgNBUu supports only NO_LABEL label."); + return meas_collected; + } if (cell_global_id.has_value()) { - // TODO: filter measurements by cell_id + logger.debug("Metric: DRB.PacketSuccessRateUlgNBUu currently does not support cell_global_id filter."); } + if (ues.size() == 0) { + // E2 level measurements. + meas_record_item_c meas_record_item; + float success_rate = 0; + uint32_t total_lost_pdus = 0; + uint32_t total_pdus = 0; + for (unsigned idx = 0; ue_aggr_rlc_metrics.size(); idx++) { + rlc_metrics& rlc_metric = ue_aggr_rlc_metrics[idx]; + total_lost_pdus += rlc_metric.rx.num_lost_pdus; + total_pdus += rlc_metric.rx.num_pdus; + } + if (total_pdus) { + success_rate = 1.0 * (total_pdus - total_lost_pdus) / total_pdus; + } + uint32_t success_rate_int = success_rate * 100; + meas_record_item.set_integer() = success_rate_int; + items.push_back(meas_record_item); + meas_collected = true; + } + + for (auto& ue : ues) { + meas_record_item_c meas_record_item; + gnb_cu_ue_f1ap_id_t gnb_cu_ue_f1ap_id = int_to_gnb_cu_ue_f1ap_id(ue.gnb_du_ueid().gnb_cu_ue_f1_ap_id); + uint32_t ue_idx = f1ap_ue_id_provider.get_ue_index(gnb_cu_ue_f1ap_id); + if (ue_aggr_rlc_metrics.count(ue_idx) == 0) { + meas_record_item.set_no_value(); + items.push_back(meas_record_item); + meas_collected = true; + continue; + } + float success_rate = 0; + if (ue_aggr_rlc_metrics[ue_idx].rx.num_pdus) { + success_rate = 1.0 * (ue_aggr_rlc_metrics[ue_idx].rx.num_pdus - ue_aggr_rlc_metrics[ue_idx].rx.num_lost_pdus) / + ue_aggr_rlc_metrics[ue_idx].rx.num_pdus; + } + uint32_t success_rate_int = success_rate * 100; + meas_record_item.set_integer() = success_rate_int; + items.push_back(meas_record_item); + meas_collected = true; + } + return meas_collected; +} + +bool e2sm_kpm_du_meas_provider_impl::get_drb_rlc_packet_drop_rate_dl( + const asn1::e2sm_kpm::label_info_list_l label_info_list, + const std::vector& ues, + const srsran::optional cell_global_id, + std::vector& items) +{ + bool meas_collected = false; - if (label_info_list.size()) { - // TODO: need to get measurements with present labels + if ((label_info_list.size() > 1 or + (label_info_list.size() == 1 and not label_info_list[0].meas_label.no_label_present))) { + logger.debug("Metric: DRB.RlcPacketDropRateDl supports only NO_LABEL label."); + return meas_collected; } - if (check_measurement_name(meas_type, "DRB.RlcPacketDropRateDl")) { + if (cell_global_id.has_value()) { + logger.debug("Metric: DRB.RlcPacketDropRateDl currently does not support cell_global_id filter."); + } + + if (ues.size() == 0) { + // E2 level measurements. + meas_record_item_c meas_record_item; + float drop_rate = 0; + uint32_t total_dropped_sdus = 0; + uint32_t total_tx_num_sdus = 0; + for (auto& rlc_metric : ue_aggr_rlc_metrics) { + total_dropped_sdus += rlc_metric.second.tx.num_dropped_sdus + rlc_metric.second.tx.num_discarded_sdus; + total_tx_num_sdus += rlc_metric.second.tx.num_sdus; + } + if (total_tx_num_sdus) { + drop_rate = 1.0 * total_dropped_sdus / total_tx_num_sdus; + } + uint32_t drop_rate_int = drop_rate * 100; + meas_record_item.set_integer() = drop_rate_int; + items.push_back(meas_record_item); + meas_collected = true; + } else { + // UE level measurements. for (auto& ue : ues) { meas_record_item_c meas_record_item; gnb_cu_ue_f1ap_id_t gnb_cu_ue_f1ap_id = int_to_gnb_cu_ue_f1ap_id(ue.gnb_du_ueid().gnb_cu_ue_f1_ap_id); uint32_t ue_idx = f1ap_ue_id_provider.get_ue_index(gnb_cu_ue_f1ap_id); - if (ue_idx > ue_aggr_rlc_metrics.size()) { + if (ue_aggr_rlc_metrics.count(ue_idx) == 0) { meas_record_item.set_no_value(); + items.push_back(meas_record_item); + meas_collected = true; continue; } float drop_rate = 0; if (ue_aggr_rlc_metrics[ue_idx].tx.num_sdus) { - uint32_t dropped_sdus = ue_aggr_rlc_metrics[ue_idx].tx.num_dropped_sdus + - ue_aggr_rlc_metrics[ue_idx].tx.num_discarded_sdus + - ue_aggr_rlc_metrics[ue_idx].tx.num_discard_failures; + uint32_t dropped_sdus = + ue_aggr_rlc_metrics[ue_idx].tx.num_dropped_sdus + ue_aggr_rlc_metrics[ue_idx].tx.num_discarded_sdus; drop_rate = 1.0 * dropped_sdus / ue_aggr_rlc_metrics[ue_idx].tx.num_sdus; } uint32_t drop_rate_int = drop_rate * 100; @@ -250,26 +424,96 @@ bool e2sm_kpm_du_meas_provider_impl::get_meas_data_drb_family( items.push_back(meas_record_item); meas_collected = true; } - } else if (check_measurement_name(meas_type, "DRB.RlcSduTransmittedVolumeDL")) { + } + return meas_collected; +} + +bool e2sm_kpm_du_meas_provider_impl::get_drb_rlc_sdu_transmitted_volume_dl( + const asn1::e2sm_kpm::label_info_list_l label_info_list, + const std::vector& ues, + const srsran::optional cell_global_id, + std::vector& items) +{ + bool meas_collected = false; + + if ((label_info_list.size() > 1 or + (label_info_list.size() == 1 and not label_info_list[0].meas_label.no_label_present))) { + logger.debug("Metric: DRB.RlcSduTransmittedVolumeDL supports only NO_LABEL label."); + return false; + } + + if (cell_global_id.has_value()) { + logger.debug("Metric: DRB.RlcSduTransmittedVolumeDL currently does not support cell_global_id filter."); + } + + if (ues.size() == 0) { + // E2 level measurements. + meas_record_item_c meas_record_item; + size_t total_tx_num_sdu_bytes = 0; + for (auto& rlc_metric : ue_aggr_rlc_metrics) { + total_tx_num_sdu_bytes += rlc_metric.second.tx.num_sdu_bytes; + } + meas_record_item.set_integer() = total_tx_num_sdu_bytes * 8 / 1000; // unit is kbit + items.push_back(meas_record_item); + meas_collected = true; + } else { + // UE level measurements. for (auto& ue : ues) { meas_record_item_c meas_record_item; gnb_cu_ue_f1ap_id_t gnb_cu_ue_f1ap_id = int_to_gnb_cu_ue_f1ap_id(ue.gnb_du_ueid().gnb_cu_ue_f1_ap_id); uint32_t ue_idx = f1ap_ue_id_provider.get_ue_index(gnb_cu_ue_f1ap_id); - if (ue_idx > ue_aggr_rlc_metrics.size()) { + if (ue_aggr_rlc_metrics.count(ue_idx) == 0) { meas_record_item.set_no_value(); + items.push_back(meas_record_item); + meas_collected = true; continue; } meas_record_item.set_integer() = ue_aggr_rlc_metrics[ue_idx].tx.num_sdu_bytes * 8 / 1000; // unit is kbit items.push_back(meas_record_item); meas_collected = true; } - } else if (check_measurement_name(meas_type, "DRB.RlcSduTransmittedVolumeUL")) { + } + return meas_collected; +} + +bool e2sm_kpm_du_meas_provider_impl::get_drb_rlc_sdu_transmitted_volume_ul( + const asn1::e2sm_kpm::label_info_list_l label_info_list, + const std::vector& ues, + const srsran::optional cell_global_id, + std::vector& items) +{ + bool meas_collected = false; + + if ((label_info_list.size() > 1 or + (label_info_list.size() == 1 and not label_info_list[0].meas_label.no_label_present))) { + logger.debug("Metric: DRB.RlcSduTransmittedVolumeUL supports only NO_LABEL label."); + return meas_collected; + } + + if (cell_global_id.has_value()) { + logger.debug("Metric: DRB.RlcSduTransmittedVolumeUL currently does not support cell_global_id filter."); + } + + if (ues.size() == 0) { + // E2 level measurements. + meas_record_item_c meas_record_item; + size_t total_rx_num_sdu_bytes = 0; + for (auto& rlc_metric : ue_aggr_rlc_metrics) { + total_rx_num_sdu_bytes += rlc_metric.second.rx.num_sdu_bytes; + } + meas_record_item.set_integer() = total_rx_num_sdu_bytes * 8 / 1000; // unit is kbit + items.push_back(meas_record_item); + meas_collected = true; + } else { + // UE level measurements. for (auto& ue : ues) { meas_record_item_c meas_record_item; gnb_cu_ue_f1ap_id_t gnb_cu_ue_f1ap_id = int_to_gnb_cu_ue_f1ap_id(ue.gnb_du_ueid().gnb_cu_ue_f1_ap_id); uint32_t ue_idx = f1ap_ue_id_provider.get_ue_index(gnb_cu_ue_f1ap_id); - if (ue_idx > ue_aggr_rlc_metrics.size()) { + if (ue_aggr_rlc_metrics.count(ue_idx) == 0) { meas_record_item.set_no_value(); + items.push_back(meas_record_item); + meas_collected = true; continue; } meas_record_item.set_integer() = ue_aggr_rlc_metrics[ue_idx].rx.num_sdu_bytes * 8 / 1000; // unit is kbit diff --git a/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_du_meas_provider_impl.h b/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_du_meas_provider_impl.h index f22081c1b6..7099b56110 100644 --- a/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_du_meas_provider_impl.h +++ b/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_du_meas_provider_impl.h @@ -22,6 +22,7 @@ #pragma once +#include "e2sm_kpm_metric_defs.h" #include "e2sm_kpm_utils.h" #include "srsran/adt/optional.h" #include "srsran/asn1/asn1_utils.h" @@ -31,6 +32,7 @@ #include "srsran/e2/e2sm/e2sm_kpm.h" #include "srsran/f1ap/du/f1ap_du.h" #include +#include namespace srsran { @@ -73,20 +75,37 @@ class e2sm_kpm_du_meas_provider_impl : public e2sm_kpm_meas_provider, public e2_ std::vector& items) override; private: - bool check_measurement_family(const asn1::e2sm_kpm::meas_type_c meas_type, const char* family_name); - bool check_measurement_name(const asn1::e2sm_kpm::meas_type_c meas_type, const char* meas); - - bool get_meas_data_drb_family(const asn1::e2sm_kpm::meas_type_c& meas_type, - const asn1::e2sm_kpm::label_info_list_l label_info_list, - const std::vector& ues, - const srsran::optional cell_global_id, - std::vector& items); - - srslog::basic_logger& logger; - srs_du::f1ap_ue_id_translator& f1ap_ue_id_provider; - std::vector supported_metrics; - std::vector last_ue_metrics; - std::vector ue_aggr_rlc_metrics; + typedef bool(metric_meas_getter_func_t)(const asn1::e2sm_kpm::label_info_list_l label_info_list, + const std::vector& ues, + const srsran::optional cell_global_id, + std::vector& items); + + typedef metric_meas_getter_func_t(e2sm_kpm_du_meas_provider_impl::*metric_meas_getter_func_ptr); + + struct e2sm_kpm_supported_metric_t { + uint32_t supported_labels; + uint32_t supported_levels; + bool cell_scope_supported; + metric_meas_getter_func_ptr func; + }; + + bool check_e2sm_kpm_metrics_definitions(span metrics_defs); + + // Measurement getter functions. + metric_meas_getter_func_t get_cqi; + metric_meas_getter_func_t get_rsrp; + metric_meas_getter_func_t get_rsrq; + metric_meas_getter_func_t get_drb_ul_success_rate; + metric_meas_getter_func_t get_drb_rlc_packet_drop_rate_dl; + metric_meas_getter_func_t get_drb_rlc_sdu_transmitted_volume_dl; + metric_meas_getter_func_t get_drb_rlc_sdu_transmitted_volume_ul; + metric_meas_getter_func_t get_drb_ul_mean_throughput; + + srslog::basic_logger& logger; + srs_du::f1ap_ue_id_translator& f1ap_ue_id_provider; + std::vector last_ue_metrics; + std::map ue_aggr_rlc_metrics; + std::map supported_metrics; }; } // namespace srsran diff --git a/lib/e2/e2sm/e2sm_kpm_metric_defs.h b/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_metric_defs.h similarity index 99% rename from lib/e2/e2sm/e2sm_kpm_metric_defs.h rename to lib/e2/e2sm/e2sm_kpm/e2sm_kpm_metric_defs.h index 8d9494eee3..acbe6751e5 100644 --- a/lib/e2/e2sm/e2sm_kpm_metric_defs.h +++ b/lib/e2/e2sm/e2sm_kpm/e2sm_kpm_metric_defs.h @@ -63,6 +63,21 @@ struct e2sm_kpm_metric_t { uint32_t optional_levels; }; +inline bool is_cell_id_required(const e2sm_kpm_metric_t& metric) +{ + // Cell ID is required if metric belongs to a measurement object class confined in a single cell. + if (metric.meas_obj == NRCellCU) { + return true; + } + + if (metric.meas_obj == NRCellDU) { + return true; + } + + // Cell ID is not needed if metric is cell agnostic (e.g., GNBCUUPFunction). + return false; +} + /// Number of E2SM-KPM metrics defined in 3GPP TS 28.552. const size_t NOF_3GPP_TS_28_552_METRICS = 278; diff --git a/lib/f1ap/common/f1ap_asn1_utils.h b/lib/f1ap/common/f1ap_asn1_utils.h index c46dc7818b..4e96e92fa6 100644 --- a/lib/f1ap/common/f1ap_asn1_utils.h +++ b/lib/f1ap/common/f1ap_asn1_utils.h @@ -24,7 +24,7 @@ #include "srsran/adt/expected.h" #include "srsran/asn1/f1ap/f1ap.h" -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/ran/paging_information.h" #include "srsran/support/error_handling.h" diff --git a/lib/f1ap/cu_cp/f1ap_cu_factory.cpp b/lib/f1ap/cu_cp/f1ap_cu_factory.cpp index 3c4b81bbc9..abfe0f7a95 100644 --- a/lib/f1ap/cu_cp/f1ap_cu_factory.cpp +++ b/lib/f1ap/cu_cp/f1ap_cu_factory.cpp @@ -31,10 +31,15 @@ using namespace srs_cu_cp; std::unique_ptr srsran::srs_cu_cp::create_f1ap(f1ap_message_notifier& f1ap_pdu_notifier_, f1ap_du_processor_notifier& f1ap_du_processor_notifier_, f1ap_du_management_notifier& f1ap_du_management_notifier_, + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier_, timer_manager& timers_, task_executor& ctrl_exec_) { - auto f1ap_cu = std::make_unique( - f1ap_pdu_notifier_, f1ap_du_processor_notifier_, f1ap_du_management_notifier_, timers_, ctrl_exec_); + auto f1ap_cu = std::make_unique(f1ap_pdu_notifier_, + f1ap_du_processor_notifier_, + f1ap_du_management_notifier_, + f1ap_cu_cp_notifier_, + timers_, + ctrl_exec_); return f1ap_cu; } diff --git a/lib/f1ap/cu_cp/f1ap_cu_impl.cpp b/lib/f1ap/cu_cp/f1ap_cu_impl.cpp index 078e3aa9b4..f05e9471c1 100644 --- a/lib/f1ap/cu_cp/f1ap_cu_impl.cpp +++ b/lib/f1ap/cu_cp/f1ap_cu_impl.cpp @@ -25,6 +25,7 @@ #include "../common/asn1_helpers.h" #include "f1ap_asn1_helpers.h" #include "srsran/asn1/f1ap/f1ap.h" +#include "srsran/cu_cp/cu_cp_types.h" #include "srsran/f1ap/common/f1ap_message.h" #include "srsran/ran/nr_cgi_helpers.h" @@ -35,6 +36,7 @@ using namespace srs_cu_cp; f1ap_cu_impl::f1ap_cu_impl(f1ap_message_notifier& f1ap_pdu_notifier_, f1ap_du_processor_notifier& f1ap_du_processor_notifier_, f1ap_du_management_notifier& f1ap_du_management_notifier_, + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier_, timer_manager& timers_, task_executor& ctrl_exec_) : logger(srslog::fetch_basic_logger("CU-CP-F1")), @@ -42,7 +44,7 @@ f1ap_cu_impl::f1ap_cu_impl(f1ap_message_notifier& f1ap_pdu_notifier_, pdu_notifier(f1ap_pdu_notifier_), du_processor_notifier(f1ap_du_processor_notifier_), du_management_notifier(f1ap_du_management_notifier_), - timers(timers_), + cu_cp_notifier(f1ap_cu_cp_notifier_), ctrl_exec(ctrl_exec_) { } @@ -94,7 +96,7 @@ void f1ap_cu_impl::handle_f1_setup_response(const f1ap_f1_setup_response& msg) void f1ap_cu_impl::handle_dl_rrc_message_transfer(const f1ap_dl_rrc_message& msg) { if (!ue_ctxt_list.contains(msg.ue_index)) { - logger.error("ue={}: Dropping DlRrcMessageTransfer. UE context does not exist", msg.ue_index); + logger.warning("ue={}: Dropping DlRrcMessageTransfer. UE context does not exist", msg.ue_index); return; } @@ -106,18 +108,13 @@ void f1ap_cu_impl::handle_dl_rrc_message_transfer(const f1ap_dl_rrc_message& msg dl_rrc_msg->srb_id = (uint8_t)msg.srb_id; dl_rrc_msg->rrc_container = msg.rrc_container.copy(); - if (msg.old_ue_index != ue_index_t::invalid) { - if (ue_ctxt_list.contains(msg.old_ue_index)) { - f1ap_ue_context& old_ue_ctxt = ue_ctxt_list[msg.old_ue_index]; - dl_rrc_msg->old_gnb_du_ue_f1ap_id_present = true; - dl_rrc_msg->old_gnb_du_ue_f1ap_id = gnb_du_ue_f1ap_id_to_uint(old_ue_ctxt.du_ue_f1ap_id); - - // Remove old UE context from F1 - ue_ctxt_list.remove_ue(old_ue_ctxt.cu_ue_f1ap_id); - } else { - logger.error( - "ue={} old_ue={}: Old F1AP UE Context for reestablishing UE not found.", msg.ue_index, msg.old_ue_index); - } + if (ue_ctxt.pending_old_ue_id.has_value()) { + // if the UE requests to reestablish RRC connection in the last serving gNB-DU, the DL RRC MESSAGE TRANSFER message + // shall include old gNB-DU UE F1AP ID, see TS 38.401 section 8.7. + dl_rrc_msg->old_gnb_du_ue_f1ap_id_present = true; + dl_rrc_msg->old_gnb_du_ue_f1ap_id = gnb_du_ue_f1ap_id_to_uint(ue_ctxt.pending_old_ue_id.value()); + + ue_ctxt.pending_old_ue_id.reset(); } // Pack message into PDU @@ -149,7 +146,7 @@ f1ap_cu_impl::handle_ue_context_setup_request(const f1ap_ue_context_setup_reques async_task f1ap_cu_impl::handle_ue_context_release_command(const f1ap_ue_context_release_command& msg) { if (!ue_ctxt_list.contains(msg.ue_index)) { - logger.error("ue={}: Dropping UeContextReleaseCommand. UE context does not exist", msg.ue_index); + logger.warning("ue={}: Dropping UeContextReleaseCommand. UE context does not exist", msg.ue_index); return launch_async([](coro_context>& ctx) mutable { CORO_BEGIN(ctx); @@ -164,7 +161,7 @@ async_task f1ap_cu_impl::handle_ue_context_modification_request(const f1ap_ue_context_modification_request& request) { if (!ue_ctxt_list.contains(request.ue_index)) { - logger.error("ue={}: Dropping UeContextModificationRequest. UE context does not exist", request.ue_index); + logger.warning("ue={}: Dropping UeContextModificationRequest. UE context does not exist", request.ue_index); return launch_async([](coro_context>& ctx) mutable { CORO_BEGIN(ctx); @@ -175,6 +172,17 @@ f1ap_cu_impl::handle_ue_context_modification_request(const f1ap_ue_context_modif return launch_async(request, ue_ctxt_list[request.ue_index], pdu_notifier, logger); } +bool f1ap_cu_impl::handle_ue_id_update(ue_index_t ue_index, ue_index_t old_ue_index) +{ + if (!ue_ctxt_list.contains(ue_index) or !ue_ctxt_list.contains(old_ue_index)) { + return false; + } + + // Mark that an old gNB-DU UE F1AP ID needs to be sent to the DU in the next DL RRC Message Transfer. + ue_ctxt_list[ue_index].pending_old_ue_id = ue_ctxt_list[old_ue_index].du_ue_f1ap_id; + return true; +} + void f1ap_cu_impl::handle_paging(const cu_cp_paging_message& msg) { asn1::f1ap::paging_s paging = {}; @@ -218,7 +226,7 @@ void f1ap_cu_impl::handle_message(const f1ap_message& msg) handle_unsuccessful_outcome(msg.pdu.unsuccessful_outcome()); break; default: - logger.error("Invalid PDU type"); + logger.warning("Invalid PDU type"); break; } })) { @@ -226,9 +234,14 @@ void f1ap_cu_impl::handle_message(const f1ap_message& msg) } } -int f1ap_cu_impl::get_nof_ues() +void f1ap_cu_impl::remove_ue_context(ue_index_t ue_index) { - return ue_ctxt_list.size(); + if (!ue_ctxt_list.contains(ue_index)) { + logger.debug("ue={}: UE context not found", ue_index); + return; + } + + ue_ctxt_list.remove_ue(ue_index); } void f1ap_cu_impl::handle_initiating_message(const asn1::f1ap::init_msg_s& msg) @@ -250,7 +263,7 @@ void f1ap_cu_impl::handle_initiating_message(const asn1::f1ap::init_msg_s& msg) handle_ue_context_release_request(msg.value.ue_context_release_request()); } break; default: - logger.error("Initiating message of type {} is not supported", msg.value.type().to_string()); + logger.warning("Initiating message of type {} is not supported", msg.value.type().to_string()); } } @@ -268,15 +281,21 @@ void f1ap_cu_impl::handle_initial_ul_rrc_message(const init_ul_rrc_msg_transfer_ { // Reject request without served cells if (not msg->du_to_cu_rrc_container_present) { - logger.error("du_ue_f1ap_id={}: Dropping InitialUlRrcMessageTransfer. Missing DU to CU container", - msg->gnb_du_ue_f1ap_id); + logger.warning("du_ue_f1ap_id={}: Dropping InitialUlRrcMessageTransfer. Missing DU to CU container", + msg->gnb_du_ue_f1ap_id); /// Assume the DU can't serve the UE. Ignoring the message. return; } nr_cell_global_id_t cgi = cgi_from_asn1(msg->nr_cgi); if (not srsran::config_helpers::is_valid(cgi)) { - logger.error("du_ue_f1ap_id={}: Dropping InitialUlRrcMessageTransfer. Invalid CGI", msg->gnb_du_ue_f1ap_id); + logger.warning("du_ue_f1ap_id={}: Dropping InitialUlRrcMessageTransfer. Invalid CGI", msg->gnb_du_ue_f1ap_id); + return; + } + + rnti_t crnti = to_rnti(msg->c_rnti); + if (crnti == INVALID_RNTI) { + logger.warning("du_ue_f1ap_id={}: Dropping InitialUlRrcMessageTransfer. Invalid RNTI", msg->gnb_du_ue_f1ap_id); return; } @@ -287,27 +306,27 @@ void f1ap_cu_impl::handle_initial_ul_rrc_message(const init_ul_rrc_msg_transfer_ cgi.plmn); if (msg->sul_access_ind_present) { - logger.debug("Ignoring SUL access indicator"); + logger.debug("du_ue_f1ap_id={}: Ignoring SUL access indicator", msg->gnb_du_ue_f1ap_id); } gnb_cu_ue_f1ap_id_t cu_ue_f1ap_id = ue_ctxt_list.next_gnb_cu_ue_f1ap_id(); if (cu_ue_f1ap_id == gnb_cu_ue_f1ap_id_t::invalid) { - logger.error("du_ue_f1ap_id={}: Dropping InitialUlRrcMessageTransfer. No CU UE F1AP ID available", - msg->gnb_du_ue_f1ap_id); + logger.warning("du_ue_f1ap_id={}: Dropping InitialUlRrcMessageTransfer. No CU UE F1AP ID available", + msg->gnb_du_ue_f1ap_id); return; } // Request UE index allocation ue_index_t ue_index = du_processor_notifier.on_new_ue_index_required(); if (ue_index == ue_index_t::invalid) { - logger.error("du_ue_f1ap_id={}: Dropping InitialUlRrcMessageTransfer. No UE Index available"); + logger.warning("du_ue_f1ap_id={}: Dropping InitialUlRrcMessageTransfer. No UE Index available"); return; } // Request UE creation cu_cp_ue_creation_message ue_creation_msg = {}; ue_creation_msg.ue_index = ue_index; - ue_creation_msg.c_rnti = to_rnti(msg->c_rnti); + ue_creation_msg.c_rnti = crnti; ue_creation_msg.cgi = cgi_from_asn1(msg->nr_cgi); if (msg->du_to_cu_rrc_container_present) { ue_creation_msg.du_to_cu_rrc_container = byte_buffer(msg->du_to_cu_rrc_container); @@ -315,6 +334,13 @@ void f1ap_cu_impl::handle_initial_ul_rrc_message(const init_ul_rrc_msg_transfer_ ue_creation_complete_message ue_creation_complete_msg = du_processor_notifier.on_create_ue(ue_creation_msg); + // Remove the UE if the creation was not successful + if (ue_creation_complete_msg.ue_index == ue_index_t::invalid) { + logger.warning("du_ue_f1ap_id={}: Removing the UE. UE creation failed"); + cu_cp_notifier.on_ue_removal_required(ue_index); + return; + } + // Create UE context and store it ue_ctxt_list.add_ue(ue_index, cu_ue_f1ap_id); ue_ctxt_list.add_rrc_notifier(ue_creation_complete_msg.ue_index, ue_creation_complete_msg.f1ap_rrc_notifier); @@ -341,9 +367,9 @@ void f1ap_cu_impl::handle_initial_ul_rrc_message(const init_ul_rrc_msg_transfer_ void f1ap_cu_impl::handle_ul_rrc_message(const ul_rrc_msg_transfer_s& msg) { if (!ue_ctxt_list.contains(int_to_gnb_cu_ue_f1ap_id(msg->gnb_cu_ue_f1ap_id))) { - logger.error("cu_ue_f1ap_id={} du_ue_f1ap_id={}: Dropping UlRrcMessageTransfer. UE context does not exist", - msg->gnb_cu_ue_f1ap_id, - msg->gnb_du_ue_f1ap_id); + logger.warning("cu_ue_f1ap_id={} du_ue_f1ap_id={}: Dropping UlRrcMessageTransfer. UE context does not exist", + msg->gnb_cu_ue_f1ap_id, + msg->gnb_du_ue_f1ap_id); return; } @@ -362,9 +388,9 @@ void f1ap_cu_impl::handle_f1_removal_request(const asn1::f1ap::f1_removal_reques void f1ap_cu_impl::handle_ue_context_release_request(const asn1::f1ap::ue_context_release_request_s& msg) { if (!ue_ctxt_list.contains(int_to_gnb_cu_ue_f1ap_id(msg->gnb_cu_ue_f1ap_id))) { - logger.error("cu_ue_f1ap_id={} du_ue_f1ap_id={}: Dropping UeContextReleaseRequest. UE context does not exist", - msg->gnb_cu_ue_f1ap_id, - msg->gnb_du_ue_f1ap_id); + logger.warning("cu_ue_f1ap_id={} du_ue_f1ap_id={}: Dropping UeContextReleaseRequest. UE context does not exist", + msg->gnb_cu_ue_f1ap_id, + msg->gnb_du_ue_f1ap_id); return; } @@ -403,7 +429,7 @@ void f1ap_cu_impl::handle_successful_outcome(const asn1::f1ap::successful_outcom .ev_mng.context_modification_outcome.set(outcome.value.ue_context_mod_resp()); } break; default: - logger.error("Successful outcome of type {} is not supported", outcome.value.type().to_string()); + logger.warning("Successful outcome of type {} is not supported", outcome.value.type().to_string()); } } @@ -419,6 +445,6 @@ void f1ap_cu_impl::handle_unsuccessful_outcome(const asn1::f1ap::unsuccessful_ou .ev_mng.context_modification_outcome.set(outcome.value.ue_context_mod_fail()); } break; default: - logger.error("Unsuccessful outcome of type {} is not supported", outcome.value.type().to_string()); + logger.warning("Unsuccessful outcome of type {} is not supported", outcome.value.type().to_string()); } } diff --git a/lib/f1ap/cu_cp/f1ap_cu_impl.h b/lib/f1ap/cu_cp/f1ap_cu_impl.h index 56d491a5ba..4b230226ac 100644 --- a/lib/f1ap/cu_cp/f1ap_cu_impl.h +++ b/lib/f1ap/cu_cp/f1ap_cu_impl.h @@ -44,6 +44,7 @@ class f1ap_cu_impl final : public f1ap_cu f1ap_cu_impl(f1ap_message_notifier& f1ap_pdu_notifier_, f1ap_du_processor_notifier& f1ap_du_processor_notifier_, f1ap_du_management_notifier& f1ap_du_management_notifier_, + f1ap_ue_removal_notifier& f1ap_cu_cp_notifier_, timer_manager& timers_, task_executor& ctrl_exec_); ~f1ap_cu_impl(); @@ -64,6 +65,8 @@ class f1ap_cu_impl final : public f1ap_cu async_task handle_ue_context_modification_request(const f1ap_ue_context_modification_request& request) override; + bool handle_ue_id_update(ue_index_t ue_index, ue_index_t old_ue_index) override; + // f1ap paging handler functions void handle_paging(const cu_cp_paging_message& msg) override; @@ -73,16 +76,20 @@ class f1ap_cu_impl final : public f1ap_cu void handle_connection_loss() override {} // f1ap statistics - int get_nof_ues() override; + size_t get_nof_ues() const override { return ue_ctxt_list.size(); }; + + // f1ap_ue_context_removal_handler functions + void remove_ue_context(ue_index_t ue_index) override; // f1ap_cu_interface - f1ap_message_handler& get_f1ap_message_handler() override { return *this; } - f1ap_event_handler& get_f1ap_event_handler() override { return *this; } - f1ap_rrc_message_handler& get_f1ap_rrc_message_handler() override { return *this; } - f1ap_connection_manager& get_f1ap_connection_manager() override { return *this; } - f1ap_ue_context_manager& get_f1ap_ue_context_manager() override { return *this; } - f1ap_statistics_handler& get_f1ap_statistics_handler() override { return *this; } - f1ap_paging_manager& get_f1ap_paging_manager() override { return *this; } + f1ap_message_handler& get_f1ap_message_handler() override { return *this; } + f1ap_event_handler& get_f1ap_event_handler() override { return *this; } + f1ap_rrc_message_handler& get_f1ap_rrc_message_handler() override { return *this; } + f1ap_connection_manager& get_f1ap_connection_manager() override { return *this; } + f1ap_ue_context_manager& get_f1ap_ue_context_manager() override { return *this; } + f1ap_statistics_handler& get_f1ap_statistics_handler() override { return *this; } + f1ap_paging_manager& get_f1ap_paging_manager() override { return *this; } + f1ap_ue_context_removal_handler& get_f1ap_ue_context_removal_handler() override { return *this; } private: /// \brief Handle the reception of an initiating message. @@ -119,8 +126,6 @@ class f1ap_cu_impl final : public f1ap_cu /// \param[in] msg The UE Context Release Request message. void handle_ue_context_release_request(const asn1::f1ap::ue_context_release_request_s& msg); - gnb_cu_ue_f1ap_id_t allocate_f1ap_id(ue_index_t ue_index); - srslog::basic_logger& logger; /// Repository of UE Contexts. @@ -130,7 +135,7 @@ class f1ap_cu_impl final : public f1ap_cu f1ap_message_notifier& pdu_notifier; f1ap_du_processor_notifier& du_processor_notifier; f1ap_du_management_notifier& du_management_notifier; - timer_manager& timers; + f1ap_ue_removal_notifier& cu_cp_notifier; task_executor& ctrl_exec; unsigned current_transaction_id = 0; // store current F1AP transaction id diff --git a/lib/f1ap/cu_cp/procedures/ue_context_release_procedure.cpp b/lib/f1ap/cu_cp/procedures/ue_context_release_procedure.cpp index 4f60635aaa..8cd8465248 100644 --- a/lib/f1ap/cu_cp/procedures/ue_context_release_procedure.cpp +++ b/lib/f1ap/cu_cp/procedures/ue_context_release_procedure.cpp @@ -102,7 +102,6 @@ ue_context_release_procedure::create_ue_context_release_complete(const asn1::f1a } else { logger.error("ue={}: \"{}\" failed.", ue_ctxt.ue_index, name()); } - ue_ctxt_list.remove_ue(ue_ctxt.cu_ue_f1ap_id); return ret; } diff --git a/lib/f1ap/cu_cp/procedures/ue_context_setup_procedure.cpp b/lib/f1ap/cu_cp/procedures/ue_context_setup_procedure.cpp index 2387fed811..f4edc8d428 100644 --- a/lib/f1ap/cu_cp/procedures/ue_context_setup_procedure.cpp +++ b/lib/f1ap/cu_cp/procedures/ue_context_setup_procedure.cpp @@ -41,6 +41,8 @@ ue_context_setup_procedure::ue_context_setup_procedure(const f1ap_ue_context_set logger(logger_), is_inter_cu_handover(is_inter_cu_handover_) { + srsran_assert(request.ue_index != ue_index_t::invalid, + "UE index of F1AP UE Context Setup Request must not be invalid"); } void ue_context_setup_procedure::operator()(coro_context>& ctx) @@ -72,12 +74,12 @@ void ue_context_setup_procedure::operator()(coro_context @@ -36,11 +36,13 @@ struct f1ap_ue_context { gnb_du_ue_f1ap_id_t du_ue_f1ap_id = gnb_du_ue_f1ap_id_t::invalid; f1ap_rrc_message_notifier* rrc_notifier = nullptr; bool marked_for_release = false; + /// Whether the old gNB-DU UE F1AP UE ID IE needs to be notified back to the DU, due to reestablishment. + optional pending_old_ue_id; f1ap_ue_transaction_manager ev_mng; - f1ap_ue_context(ue_index_t ue_idx_, gnb_cu_ue_f1ap_id_t cu_ue_f1ap_id_, timer_factory timers_) : - ue_index(ue_idx_), cu_ue_f1ap_id(cu_ue_f1ap_id_), ev_mng(timers_) + f1ap_ue_context(ue_index_t ue_index_, gnb_cu_ue_f1ap_id_t cu_ue_f1ap_id_, timer_factory timers_) : + ue_index(ue_index_), cu_ue_f1ap_id(cu_ue_f1ap_id_), ev_mng(timers_) { } }; @@ -55,12 +57,12 @@ class f1ap_ue_context_list /// \brief Checks whether a UE with the given UE index exists. /// \param[in] ue_index The UE index used to find the UE. /// \return The CU UE ID. - bool contains(ue_index_t ue_idx) const + bool contains(ue_index_t ue_index) const { - if (ue_index_to_ue_f1ap_id.find(ue_idx) == ue_index_to_ue_f1ap_id.end()) { + if (ue_index_to_ue_f1ap_id.find(ue_index) == ue_index_to_ue_f1ap_id.end()) { return false; } - if (ues.find(ue_index_to_ue_f1ap_id.at(ue_idx)) == ues.end()) { + if (ues.find(ue_index_to_ue_f1ap_id.at(ue_index)) == ues.end()) { return false; } return true; @@ -68,43 +70,71 @@ class f1ap_ue_context_list f1ap_ue_context& operator[](gnb_cu_ue_f1ap_id_t cu_ue_id) { - srsran_assert(ues.find(cu_ue_id) != ues.end(), "F1AP UE context for cu_ue_f1ap_id={} not found.", cu_ue_id); + srsran_assert(ues.find(cu_ue_id) != ues.end(), "cu_ue_f1ap_id={}: F1AP UE context not found", cu_ue_id); return ues.at(cu_ue_id); } - f1ap_ue_context& operator[](ue_index_t ue_idx) + f1ap_ue_context& operator[](ue_index_t ue_index) { - srsran_assert(ue_index_to_ue_f1ap_id.find(ue_idx) != ue_index_to_ue_f1ap_id.end(), - "ue={} gNB-CU-UE-F1AP-ID not found.", - ue_idx); - srsran_assert(ues.find(ue_index_to_ue_f1ap_id.at(ue_idx)) != ues.end(), - "F1AP UE context for cu_ue_f1ap_id={} not found.", - ue_index_to_ue_f1ap_id.at(ue_idx)); - return ues.at(ue_index_to_ue_f1ap_id.at(ue_idx)); + srsran_assert(ue_index_to_ue_f1ap_id.find(ue_index) != ue_index_to_ue_f1ap_id.end(), + "ue={} gNB-CU-UE-F1AP-ID not found", + ue_index); + srsran_assert(ues.find(ue_index_to_ue_f1ap_id.at(ue_index)) != ues.end(), + "cu_ue_f1ap_id={}: F1AP UE context not found", + ue_index_to_ue_f1ap_id.at(ue_index)); + return ues.at(ue_index_to_ue_f1ap_id.at(ue_index)); + } + + const f1ap_ue_context* find(gnb_du_ue_f1ap_id_t du_ue_id) const + { + auto it = std::find_if( + ues.begin(), ues.end(), [du_ue_id](const std::pair& u) { + return u.second.du_ue_f1ap_id == du_ue_id; + }); + return it != ues.end() ? &it->second : nullptr; } - f1ap_ue_context& add_ue(ue_index_t ue_idx, gnb_cu_ue_f1ap_id_t cu_ue_id) + f1ap_ue_context& add_ue(ue_index_t ue_index, gnb_cu_ue_f1ap_id_t cu_ue_id) { - logger.debug("ue={} cu_ue_f1ap_id={} Adding F1AP UE context.", ue_idx, cu_ue_id); + srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); + srsran_assert(cu_ue_id != gnb_cu_ue_f1ap_id_t::invalid, "Invalid cu_ue_id={}", cu_ue_id); + + logger.debug("ue={} cu_ue_f1ap_id={}: Adding F1AP UE context", ue_index, cu_ue_id); ues.emplace( - std::piecewise_construct, std::forward_as_tuple(cu_ue_id), std::forward_as_tuple(ue_idx, cu_ue_id, timers)); - ue_index_to_ue_f1ap_id.emplace(ue_idx, cu_ue_id); + std::piecewise_construct, std::forward_as_tuple(cu_ue_id), std::forward_as_tuple(ue_index, cu_ue_id, timers)); + ue_index_to_ue_f1ap_id.emplace(ue_index, cu_ue_id); return ues.at(cu_ue_id); } - void remove_ue(gnb_cu_ue_f1ap_id_t cu_ue_id) + void remove_ue(ue_index_t ue_index) { - logger.debug("ue={} cu_ue_f1ap_id={} Removing F1AP UE context.", ues.at(cu_ue_id).ue_index, cu_ue_id); - ue_index_to_ue_f1ap_id.erase(ues.at(cu_ue_id).ue_index); + srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); + + if (ue_index_to_ue_f1ap_id.find(ue_index) == ue_index_to_ue_f1ap_id.end()) { + logger.warning("ue={}: gNB-CU-UE-F1AP-ID not found", ue_index); + return; + } + + // Remove UE from lookup + gnb_cu_ue_f1ap_id_t cu_ue_id = ue_index_to_ue_f1ap_id.at(ue_index); + ue_index_to_ue_f1ap_id.erase(ue_index); + + if (ues.find(cu_ue_id) == ues.end()) { + logger.warning("cu_ue_f1ap_id={}: F1AP UE context not found", cu_ue_id); + return; + } + + logger.debug("ue={} cu_ue_f1ap_id={}: Removing F1AP UE context", ue_index, cu_ue_id); ues.erase(cu_ue_id); } void add_rrc_notifier(ue_index_t ue_index, f1ap_rrc_message_notifier* notifier) { + srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); srsran_assert(ue_index_to_ue_f1ap_id.find(ue_index) != ue_index_to_ue_f1ap_id.end(), - "ue={} gNB-CU-UE-F1AP-ID not found.", + "ue={}: gNB-CU-UE-F1AP-ID not found", ue_index); srsran_assert(ues.find(ue_index_to_ue_f1ap_id.at(ue_index)) != ues.end(), - "F1AP UE context for cu_ue_f1ap_id={} not found.", + "cu_ue_f1ap_id={}: F1AP UE context not found", ue_index_to_ue_f1ap_id.at(ue_index)); ues.at(ue_index_to_ue_f1ap_id.at(ue_index)).rrc_notifier = notifier; } diff --git a/lib/f1ap/du/f1ap_du_impl.cpp b/lib/f1ap/du/f1ap_du_impl.cpp index 31abbec420..cc777f96ee 100644 --- a/lib/f1ap/du/f1ap_du_impl.cpp +++ b/lib/f1ap/du/f1ap_du_impl.cpp @@ -84,7 +84,13 @@ async_task f1ap_du_impl::handle_f1_setup_request(cons f1ap_ue_creation_response f1ap_du_impl::handle_ue_creation_request(const f1ap_ue_creation_request& msg) { - return create_f1ap_ue(msg, ues, ctxt, *events); + f1ap_ue_creation_response resp = create_f1ap_ue(msg, ues, ctxt, *events); + if (resp.result) { + logger.info("{}: F1 UE context created successfully.", ues[msg.ue_index].context); + } else { + logger.warning("ue={} crnti={}: F1 UE context failed to be created.", msg.ue_index, msg.c_rnti); + } + return resp; } f1ap_ue_configuration_response f1ap_du_impl::handle_ue_configuration_request(const f1ap_ue_configuration_request& msg) @@ -94,6 +100,9 @@ f1ap_ue_configuration_response f1ap_du_impl::handle_ue_configuration_request(con void f1ap_du_impl::handle_ue_deletion_request(du_ue_index_t ue_index) { + if (ues.contains(ue_index)) { + logger.info("{}: F1 UE context removed.", ues[ue_index].context); + } ues.remove_ue(ue_index); } @@ -217,7 +226,10 @@ void f1ap_du_impl::handle_dl_rrc_message_transfer(const asn1::f1ap::dl_rrc_msg_t // Forward SDU to lower layers. byte_buffer sdu; - sdu.append(msg->rrc_container); + if (not sdu.append(msg->rrc_container)) { + logger.error("Discarding DlRrcMessageTransfer, could not append RRC container to SDU. srb_id={}", srb_id); + return; + } srb_bearer->handle_pdu(std::move(sdu)); } @@ -528,4 +540,4 @@ du_ue_index_t f1ap_du_impl::get_ue_index(const gnb_cu_ue_f1ap_id_t& gnb_cu_ue_f1 du_ue_index = ue->context.ue_index; } return du_ue_index; -} \ No newline at end of file +} diff --git a/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.cpp b/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.cpp index 23844e9873..31f331afad 100644 --- a/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.cpp +++ b/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.cpp @@ -31,7 +31,7 @@ using namespace asn1::f1ap; f1ap_du_ue_context_modification_procedure::f1ap_du_ue_context_modification_procedure( const asn1::f1ap::ue_context_mod_request_s& msg, f1ap_du_ue& ue_) : - ue(ue_) + ue(ue_), logger(srslog::fetch_basic_logger("F1AP-DU")) { create_du_request(msg); } @@ -141,8 +141,8 @@ void f1ap_du_ue_context_modification_procedure::send_ue_context_modification_res // > DU-to-CU RRC Container. if (not du_response.du_to_cu_rrc_container.empty()) { - resp->du_to_cu_rrc_info_present = true; - resp->du_to_cu_rrc_info.cell_group_cfg.append(du_response.du_to_cu_rrc_container); + resp->du_to_cu_rrc_info_present = true; + resp->du_to_cu_rrc_info.cell_group_cfg = du_response.du_to_cu_rrc_container.copy(); } // > Full Config IE. diff --git a/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.h b/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.h index 9870499add..802d671665 100644 --- a/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.h +++ b/lib/f1ap/du/procedures/f1ap_du_ue_context_modification_procedure.h @@ -44,7 +44,9 @@ class f1ap_du_ue_context_modification_procedure f1ap_ue_context_update_request du_request; f1ap_ue_context_update_response du_response; + + srslog::basic_logger& logger; }; } // namespace srs_du -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/lib/f1ap/du/procedures/f1ap_du_ue_context_setup_procedure.cpp b/lib/f1ap/du/procedures/f1ap_du_ue_context_setup_procedure.cpp index 7837ee4850..cfc37af084 100644 --- a/lib/f1ap/du/procedures/f1ap_du_ue_context_setup_procedure.cpp +++ b/lib/f1ap/du/procedures/f1ap_du_ue_context_setup_procedure.cpp @@ -46,7 +46,7 @@ void f1ap_du_ue_context_setup_procedure::operator()(coro_contextgnb_du_ue_f1ap_id); ue = ue_mng.find(gnb_du_ue_f1ap_id); if (ue == nullptr) { - logger.warning("Discarding UeContextSetupRequest cause=Unrecognized gNB-DU UE F1AP ID={}", gnb_du_ue_f1ap_id); + logger.warning("Discarding UeContextSetupRequest Cause: Unrecognized gNB-DU UE F1AP ID={}", gnb_du_ue_f1ap_id); send_ue_context_setup_failure(); CORO_EARLY_RETURN(); } @@ -156,7 +156,10 @@ void f1ap_du_ue_context_setup_procedure::send_ue_context_setup_response() resp->gnb_cu_ue_f1ap_id = gnb_cu_ue_f1ap_id_to_uint(ue->context.gnb_cu_ue_f1ap_id); // > DU-to-CU RRC Container. - resp->du_to_cu_rrc_info.cell_group_cfg.append(du_ue_cfg_response.du_to_cu_rrc_container); + if (not resp->du_to_cu_rrc_info.cell_group_cfg.append(du_ue_cfg_response.du_to_cu_rrc_container)) { + logger.error("Failed to append DU to CU container gNB-CU UE F1AP ID={}.", msg->gnb_cu_ue_f1ap_id); + return; + } // > Check if DU-to-CU RRC Container is a full cellGroupConfig or a delta. if (du_ue_cfg_response.full_config_present) { diff --git a/lib/f1ap/du/ue_context/f1ap_du_ue.h b/lib/f1ap/du/ue_context/f1ap_du_ue.h index 332e45d1b3..8b11964f7f 100644 --- a/lib/f1ap/du/ue_context/f1ap_du_ue.h +++ b/lib/f1ap/du/ue_context/f1ap_du_ue.h @@ -26,7 +26,7 @@ #include "ue_bearer_manager.h" #include "srsran/adt/slotted_array.h" #include "srsran/asn1/f1ap/f1ap_pdu_contents_ue.h" -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/f1ap/du/f1ap_du.h" #include "srsran/ran/du_types.h" diff --git a/lib/f1ap/du/ue_context/f1ap_du_ue_manager.h b/lib/f1ap/du/ue_context/f1ap_du_ue_manager.h index a45e0b4cc0..f3981e5110 100644 --- a/lib/f1ap/du/ue_context/f1ap_du_ue_manager.h +++ b/lib/f1ap/du/ue_context/f1ap_du_ue_manager.h @@ -51,7 +51,7 @@ class f1ap_du_ue_manager gnb_du_ue_f1ap_id_t f1ap_id = static_cast(next_gnb_f1ap_du_ue_id++); ues.emplace( - ue_index, ue_index, f1ap_id, du_handler, f1ap_msg_notifier, ctrl_exec, ue_exec_mapper.executor(ue_index)); + ue_index, ue_index, f1ap_id, du_handler, f1ap_msg_notifier, ctrl_exec, ue_exec_mapper.ctrl_executor(ue_index)); { std::lock_guard lock(map_mutex); @@ -61,14 +61,17 @@ class f1ap_du_ue_manager return ues[ue_index]; } - void remove_ue(du_ue_index_t ue_index) + bool remove_ue(du_ue_index_t ue_index) { - srsran_assert(ues.contains(ue_index), "ueId={} does not exist", ue_index); + if (not ues.contains(ue_index)) { + return false; + } { std::lock_guard lock(map_mutex); f1ap_ue_id_to_du_ue_id_map.erase(ues[ue_index].context.gnb_du_ue_f1ap_id); } ues.erase(ue_index); + return true; } f1ap_du_ue& operator[](du_ue_index_t ue_index) { return ues[ue_index]; } diff --git a/lib/f1ap/du/ue_context/f1ap_ue_context.h b/lib/f1ap/du/ue_context/f1ap_ue_context.h index 5178f8d609..3671617441 100644 --- a/lib/f1ap/du/ue_context/f1ap_ue_context.h +++ b/lib/f1ap/du/ue_context/f1ap_ue_context.h @@ -22,9 +22,10 @@ #pragma once -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/ran/du_types.h" #include "srsran/ran/rnti.h" +#include "fmt/format.h" namespace srsran { namespace srs_du { @@ -44,5 +45,32 @@ struct f1ap_ue_context { }; } // namespace srs_du +} // namespace srsran -} // namespace srsran \ No newline at end of file +namespace fmt { + +template <> +struct formatter { + template + auto parse(ParseContext& ctx) + { + return ctx.begin(); + } + + template + auto format(const srsran::srs_du::f1ap_ue_context& ue, FormatContext& ctx) + { + if (ue.gnb_cu_ue_f1ap_id == srsran::gnb_cu_ue_f1ap_id_t::invalid) { + return format_to( + ctx.out(), "ue={} c-rnti={:#x} GNB-DU-UE-F1AP-ID={}", ue.ue_index, ue.rnti, ue.gnb_du_ue_f1ap_id); + } + return format_to(ctx.out(), + "ue={} c-rnti={:#x} GNB-DU-UE-F1AP-ID={} GNB-CU-UE-F1AP-ID={}", + ue.ue_index, + ue.rnti, + ue.gnb_du_ue_f1ap_id, + ue.gnb_cu_ue_f1ap_id); + } +}; + +} // namespace fmt \ No newline at end of file diff --git a/lib/f1ap/du/ue_context/f1c_du_bearer_impl.cpp b/lib/f1ap/du/ue_context/f1c_du_bearer_impl.cpp index 86c7f15e60..bbb221a202 100644 --- a/lib/f1ap/du/ue_context/f1c_du_bearer_impl.cpp +++ b/lib/f1ap/du/ue_context/f1c_du_bearer_impl.cpp @@ -73,26 +73,19 @@ void f1c_srb0_du_bearer::handle_sdu(byte_buffer_chain sdu) // Notify upper layers of the initial UL RRC Message Transfer. f1ap_notifier.on_new_message(msg); - logger.info("Tx PDU ue={} rnti={} GNB-DU-UE-F1AP-ID={} SRB0: Initial UL RRC Message Transfer", - ue_ctxt.ue_index, - ue_ctxt.rnti, - ue_ctxt.gnb_du_ue_f1ap_id); + logger.info("UL {} SRB0 Tx PDU: Initial UL RRC Message Transfer", ue_ctxt); })) { - logger.error("ue={}: Discarding SRB0 Tx PDU. Cause: The task executor queue is full.", ue_ctxt.ue_index); + logger.error("UL {} SRB0: Discarding Tx PDU. Cause: The task executor queue is full.", ue_ctxt); } } void f1c_srb0_du_bearer::handle_pdu(byte_buffer pdu) { - logger.info("Rx PDU ue={} rnti={} GNB-DU-UE-F1AP-ID={} GNB-CU-UE-F1AP-ID={} SRB0: DL RRC Message Transfer", - ue_ctxt.ue_index, - ue_ctxt.rnti, - ue_ctxt.gnb_du_ue_f1ap_id, - ue_ctxt.gnb_cu_ue_f1ap_id); + logger.info("DL {} SRB0 Rx PDU: DL RRC Message Transfer", ue_ctxt); // Change to UE execution context before forwarding the SDU to lower layers. if (not ue_exec.execute([this, sdu = std::move(pdu)]() mutable { sdu_notifier.on_new_sdu(std::move(sdu), {}); })) { - logger.error("ue={}: Discarding SRB0 Rx PDU. Cause: The task executor queue is full.", ue_ctxt.ue_index); + logger.error("DL {} SRB0 Rx PDU: Discarding Rx PDU. Cause: The task executor queue is full.", ue_ctxt); } } @@ -116,6 +109,17 @@ void f1c_other_srb_du_bearer::handle_sdu(byte_buffer_chain sdu) { // Ensure SRB tasks are handled within the control executor as they involve access to the UE context. if (not ctrl_exec.execute([this, sdu = std::move(sdu)]() { + gnb_cu_ue_f1ap_id_t cu_ue_id = ue_ctxt.gnb_cu_ue_f1ap_id; + if (cu_ue_id >= gnb_cu_ue_f1ap_id_t::max) { + logger.warning( + "ue={} rnti={} GNB-DU-UE-F1AP-ID={} SRB={}: Discarding F1AP RX SDU. Cause: GNB-CU-UE-F1AP-ID is invalid.", + ue_ctxt.ue_index, + ue_ctxt.rnti, + ue_ctxt.gnb_du_ue_f1ap_id, + srb_id_to_uint(srb_id)); + return; + } + f1ap_message msg; // Fill F1AP UL RRC Message Transfer. @@ -130,15 +134,10 @@ void f1c_other_srb_du_bearer::handle_sdu(byte_buffer_chain sdu) f1ap_notifier.on_new_message(msg); - logger.info("Tx PDU ue={} rnti={} GNB-DU-UE-F1AP-ID={} GNB-CU-UE-F1AP-ID={} SRB{}: UL RRC Message Transfer", - ue_ctxt.ue_index, - ue_ctxt.rnti, - ue_ctxt.gnb_du_ue_f1ap_id, - ue_ctxt.gnb_cu_ue_f1ap_id, - srb_id_to_uint(srb_id)); + logger.info("UL {} SRB{} Tx PDU: UL RRC Message Transfer", ue_ctxt, srb_id_to_uint(srb_id)); })) { - logger.error("ue={} SRB={}: Discarding SRB Tx PDU. Cause: The task executor queue is full.", - ue_ctxt.ue_index, + logger.error("UL {} SRB{} Tx PDU: Discarding Tx PDU. Cause: The task executor queue is full.", + ue_ctxt, srb_id_to_uint(srb_id)); } } @@ -146,30 +145,19 @@ void f1c_other_srb_du_bearer::handle_sdu(byte_buffer_chain sdu) void f1c_other_srb_du_bearer::handle_pdu(srsran::byte_buffer pdu) { if (pdu.length() < 3) { - logger.info( - "Rx PDU ue={} rnti={} GNB-DU-UE-F1AP-ID={} GNB-CU-UE-F1AP-ID={} SRB{}: Invalid PDU length. Dropping PDU.", - ue_ctxt.ue_index, - ue_ctxt.rnti, - ue_ctxt.gnb_du_ue_f1ap_id, - ue_ctxt.gnb_cu_ue_f1ap_id, - srb_id_to_uint(srb_id)); + logger.warning("DL {} SRB{} Rx PDU: Invalid PDU length. Dropping PDU.", ue_ctxt, srb_id_to_uint(srb_id)); return; } - logger.info("Rx PDU ue={} rnti={} GNB-DU-UE-F1AP-ID={} GNB-CU-UE-F1AP-ID={} SRB{}: DL RRC Message Transfer", - ue_ctxt.ue_index, - ue_ctxt.rnti, - ue_ctxt.gnb_du_ue_f1ap_id, - ue_ctxt.gnb_cu_ue_f1ap_id, - srb_id_to_uint(srb_id)); uint32_t pdcp_sn = get_srb_pdcp_sn(pdu); // Change to UE execution context before forwarding the SDU to lower layers. if (not ue_exec.execute( [this, sdu = std::move(pdu), pdcp_sn]() mutable { sdu_notifier.on_new_sdu(std::move(sdu), pdcp_sn); })) { - logger.error("ue={} SRB{}: Discarding SRB Rx PDU. Cause: The task executor queue is full.", - ue_ctxt.ue_index, - srb_id_to_uint(srb_id)); + logger.error( + "{} SRB{} Rx PDU: Discarding Rx PDU. Cause: The task executor queue is full.", ue_ctxt, srb_id_to_uint(srb_id)); + } else { + logger.info("DL {} SRB{} Rx PDU: DL RRC Message Transfer", ue_ctxt, srb_id_to_uint(srb_id)); } } diff --git a/lib/f1ap/du/ue_context/f1c_du_bearer_impl.h b/lib/f1ap/du/ue_context/f1c_du_bearer_impl.h index a8e338460f..86da3e9a43 100644 --- a/lib/f1ap/du/ue_context/f1c_du_bearer_impl.h +++ b/lib/f1ap/du/ue_context/f1c_du_bearer_impl.h @@ -23,7 +23,7 @@ #pragma once #include "f1ap_ue_context.h" -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/f1ap/du/f1ap_du.h" #include "srsran/f1ap/du/f1c_bearer.h" #include "srsran/ran/rnti.h" diff --git a/lib/fapi_adaptor/CMakeLists.txt b/lib/fapi_adaptor/CMakeLists.txt index f8198d9fb9..99d84bf39d 100644 --- a/lib/fapi_adaptor/CMakeLists.txt +++ b/lib/fapi_adaptor/CMakeLists.txt @@ -21,10 +21,20 @@ add_subdirectory(mac) add_subdirectory(phy) -set(SOURCES +set(PRECODING_SOURCES precoding_matrix_mapper.cpp precoding_matrix_repository.cpp - precoding_matrix_table_generator.cpp) + precoding_matrix_table_generator.cpp + ) -add_library(srsran_fapi_precoding_matrix_tools STATIC ${SOURCES}) +set(UCI_SOURCES + uci_part2_correspondence_generator.cpp + uci_part2_correspondence_mapper.cpp + uci_part2_correspondence_repository.cpp + ) + +add_library(srsran_fapi_precoding_matrix_tools STATIC ${PRECODING_SOURCES}) target_link_libraries(srsran_fapi_precoding_matrix_tools srsran_ran srslog) + +add_library(srsran_fapi_uci_part2_tools STATIC ${UCI_SOURCES}) +target_link_libraries(srsran_fapi_uci_part2_tools srsran_ran srslog) diff --git a/lib/fapi_adaptor/phy/fapi_to_phy_translator.cpp b/lib/fapi_adaptor/phy/fapi_to_phy_translator.cpp index 850a46f0a4..5ef872810a 100644 --- a/lib/fapi_adaptor/phy/fapi_to_phy_translator.cpp +++ b/lib/fapi_adaptor/phy/fapi_to_phy_translator.cpp @@ -114,7 +114,7 @@ namespace { /// Helper struct to store the downlink channel PHY PDUs. struct downlink_pdus { static_vector pdcch; - static_vector pdsch; + static_vector pdsch; static_vector ssb; static_vector csi_rs; }; @@ -181,10 +181,7 @@ static downlink_pdus translate_dl_tti_pdus_to_phy_pdus(const fapi::dl_tti_reques switch (pdu.pdu_type) { case fapi::dl_pdu_type::CSI_RS: { if (pdu.csi_rs_pdu.type != csi_rs_type::CSI_RS_NZP && pdu.csi_rs_pdu.type != csi_rs_type::CSI_RS_ZP) { - logger.warning( - "Only NZP-CSI-RS and ZP-CSI-RS PDU types are supported. Skipping DL_TTI.request message in {}.{}.", - msg.sfn, - msg.slot); + logger.warning("Only NZP-CSI-RS and ZP-CSI-RS PDU types are supported. Skipping DL_TTI.request"); return {}; } @@ -195,8 +192,7 @@ static downlink_pdus translate_dl_tti_pdus_to_phy_pdus(const fapi::dl_tti_reques nzp_csi_rs_generator::config_t& csi_pdu = pdus.csi_rs.emplace_back(); convert_csi_rs_fapi_to_phy(csi_pdu, pdu.csi_rs_pdu, msg.sfn, msg.slot, cell_bandwidth_prb); if (!dl_pdu_validator.is_valid(csi_pdu)) { - logger.warning( - "Unsupported CSI-RS PDU detected. Skipping DL_TTI.request message in {}.{}.", msg.sfn, msg.slot); + logger.warning("Upper PHY flagged a CSI-RS PDU as having an invalid configuration. Skipping DL_TTI.request"); return {}; } @@ -208,8 +204,9 @@ static downlink_pdus translate_dl_tti_pdus_to_phy_pdus(const fapi::dl_tti_reques pdcch_processor::pdu_t& pdcch_pdu = pdus.pdcch.emplace_back(); convert_pdcch_fapi_to_phy(pdcch_pdu, pdu.pdcch_pdu, msg.sfn, msg.slot, i_dci, pm_repo); if (!dl_pdu_validator.is_valid(pdcch_pdu)) { - logger.warning( - "Unsupported DL DCI {} detected. Skipping DL_DCI.request message in {}.{}.", i_dci, msg.sfn, msg.slot); + logger.warning("Upper PHY flagged a DL DCI PDU with index '{}' as having an invalid configuration. " + "Skipping DL_TTI.request", + i_dci); return {}; } @@ -220,8 +217,7 @@ static downlink_pdus translate_dl_tti_pdus_to_phy_pdus(const fapi::dl_tti_reques pdsch_processor::pdu_t& pdsch_pdu = pdus.pdsch.emplace_back(); convert_pdsch_fapi_to_phy(pdsch_pdu, pdu.pdsch_pdu, msg.sfn, msg.slot, csi_re_patterns, pm_repo); if (!dl_pdu_validator.is_valid(pdsch_pdu)) { - logger.warning( - "Unsupported PDSCH PDU detected. Skipping DL_TTI.request message in {}.{}.", msg.sfn, msg.slot); + logger.warning("Upper PHY flagged a PDSCH PDU as having an invalid configuration. Skipping DL_TTI.request"); return {}; } @@ -231,14 +227,14 @@ static downlink_pdus translate_dl_tti_pdus_to_phy_pdus(const fapi::dl_tti_reques ssb_processor::pdu_t& ssb_pdu = pdus.ssb.emplace_back(); convert_ssb_fapi_to_phy(ssb_pdu, pdu.ssb_pdu, msg.sfn, msg.slot, scs_common); if (!dl_pdu_validator.is_valid(ssb_pdu)) { - logger.warning("Unsupported SSB PDU detected. Skipping DL_TTI.request message in {}.{}.", msg.sfn, msg.slot); + logger.warning("Upper PHY flagged a SSB PDU as having an invalid configuration. Skipping DL_TTI.request"); return {}; } break; } default: - srsran_assert(0, "DL_TTI.request PDU type value ({}) not recognized.", static_cast(pdu.pdu_type)); + srsran_assert(0, "DL_TTI.request PDU type value '{}' not recognized.", static_cast(pdu.pdu_type)); } } @@ -254,12 +250,7 @@ void fapi_to_phy_translator::dl_tti_request(const fapi::dl_tti_request_message& // Ignore messages that do not correspond to the current slot. if (!is_message_in_time(msg)) { - logger.warning("Real-time failure in FAPI: Received late DL_TTI.request. Current slot is {}.{} while message " - "corresponds to {}.{}", - current_slot_controller.get_slot().sfn(), - current_slot_controller.get_slot().slot_index(), - msg.sfn, - msg.slot); + logger.warning("Real-time failure in FAPI: Received late DL_TTI.request from slot {}.{}", msg.sfn, msg.slot); l2_tracer << instant_trace_event{"dl_tti_req_late", instant_trace_event::cpu_scope::global}; return; } @@ -345,7 +336,7 @@ static uplink_pdus translate_ul_tti_pdus_to_phy_pdus(const fapi::ul_tti_request_ convert_prach_fapi_to_phy(context, pdu.prach_pdu, prach_cfg, carrier_cfg, msg.sfn, msg.slot, sector_id); if (!ul_pdu_validator.is_valid(get_prach_dectector_config_from(context))) { logger.warning( - "Unsupported PRACH PDU detected. Skipping UL_TTI.request message in {}.{}.", msg.sfn, msg.slot); + "Upper PHY flagged a PRACH PDU as having an invalid configuration. Skipping UL_TTI.request in slot"); return {}; } @@ -354,10 +345,9 @@ static uplink_pdus translate_ul_tti_pdus_to_phy_pdus(const fapi::ul_tti_request_ } case fapi::ul_pdu_type::PUCCH: { uplink_processor::pucch_pdu& ul_pdu = pdus.pucch.emplace_back(); - convert_pucch_fapi_to_phy(ul_pdu, pdu.pucch_pdu, msg.sfn, msg.slot); + convert_pucch_fapi_to_phy(ul_pdu, pdu.pucch_pdu, msg.sfn, msg.slot, carrier_cfg.num_rx_ant); if (!is_pucch_pdu_valid(ul_pdu_validator, ul_pdu)) { - logger.warning( - "Unsupported PUCCH PDU detected. Skipping UL_TTI.request message in {}.{}.", msg.sfn, msg.slot); + logger.warning("Upper PHY flagged a PUCCH PDU as having an invalid configuration. Skipping UL_TTI.request"); return {}; } @@ -368,8 +358,7 @@ static uplink_pdus translate_ul_tti_pdus_to_phy_pdus(const fapi::ul_tti_request_ uplink_processor::pusch_pdu& ul_pdu = pdus.pusch.emplace_back(); convert_pusch_fapi_to_phy(ul_pdu, pdu.pusch_pdu, msg.sfn, msg.slot, carrier_cfg.num_rx_ant); if (!ul_pdu_validator.is_valid(ul_pdu.pdu)) { - logger.warning( - "Unsupported PUSCH PDU detected. Skipping UL_TTI.request message in {}.{}.", msg.sfn, msg.slot); + logger.warning("Upper PHY flagged a PUSCH PDU as having an invalid configuration. Skipping UL_TTI.request"); return {}; } @@ -377,7 +366,7 @@ static uplink_pdus translate_ul_tti_pdus_to_phy_pdus(const fapi::ul_tti_request_ } case fapi::ul_pdu_type::SRS: default: - srsran_assert(0, "UL_TTI.request PDU type value ({}) not recognized.", static_cast(pdu.pdu_type)); + srsran_assert(0, "UL_TTI.request PDU type value '{}' not recognized.", static_cast(pdu.pdu_type)); } } return pdus; @@ -391,12 +380,7 @@ void fapi_to_phy_translator::ul_tti_request(const fapi::ul_tti_request_message& // Ignore messages that do not correspond to the current slot. if (!is_message_in_time(msg)) { - logger.warning("Real-time failure in FAPI: Received UL_TTI.request message out of time. Current slot is {}.{} " - "while message corresponds to {}.{}", - current_slot_controller.get_slot().sfn(), - current_slot_controller.get_slot().slot_index(), - msg.sfn, - msg.slot); + logger.warning("Real-time failure in FAPI: Received UL_TTI.request message from slot {}.{}", msg.sfn, msg.slot); l2_tracer << instant_trace_event{"ul_tti_req_late", instant_trace_event::cpu_scope::global}; return; } @@ -441,12 +425,7 @@ void fapi_to_phy_translator::ul_dci_request(const fapi::ul_dci_request_message& // Ignore messages that do not correspond to the current slot. if (!is_message_in_time(msg)) { - logger.warning("Real-time failure in FAPI: Received late UL_DCI.request message. Current slot is {}.{} while " - "message corresponds to {}.{}", - current_slot_controller.get_slot().sfn(), - current_slot_controller.get_slot().slot_index(), - msg.sfn, - msg.slot); + logger.warning("Real-time failure in FAPI: Received UL_DCI.request message from slot {}.{}", msg.sfn, msg.slot); l2_tracer << instant_trace_event{"ul_dci_req_late", instant_trace_event::cpu_scope::global}; return; } @@ -458,8 +437,10 @@ void fapi_to_phy_translator::ul_dci_request(const fapi::ul_dci_request_message& pdcch_processor::pdu_t& pdcch_pdu = pdus.emplace_back(); convert_pdcch_fapi_to_phy(pdcch_pdu, pdu.pdu, msg.sfn, msg.slot, i_dci, *pm_repo); if (!dl_pdu_validator.is_valid(pdcch_pdu)) { - logger.warning( - "Unsupported UL DCI {} detected. Skipping UL_DCI.request message in {}.{}.", i_dci, msg.sfn, msg.slot); + logger.warning("Upper PHY flagged a UL DCI PDU with index '{}' as having an invalid configuration. Skipping " + "UL_DCI.request", + i_dci); + return; } } @@ -476,18 +457,13 @@ void fapi_to_phy_translator::tx_data_request(const fapi::tx_data_request_message // Ignore messages that do not correspond to the current slot. if (!is_message_in_time(msg)) { - logger.warning("Real-time failure in FAPI: Received late TX_Data.request. Current slot is {}.{} while message " - "corresponds to {}.{}", - current_slot_controller.get_slot().sfn(), - current_slot_controller.get_slot().slot_index(), - msg.sfn, - msg.slot); + logger.warning("Real-time failure in FAPI: Received TX_Data.request from slot {}.{}", msg.sfn, msg.slot); l2_tracer << instant_trace_event{"tx_data_req_late", instant_trace_event::cpu_scope::global}; return; } if (msg.pdus.size() != pdsch_pdu_repository.size()) { - logger.warning("Invalid TX_Data.request. Message contains ({}) payload PDUs but expected ({})", + logger.warning("Invalid TX_Data.request. Message contains '{}' payload PDUs but expected '{}'", msg.pdus.size(), pdsch_pdu_repository.size()); return; @@ -519,4 +495,7 @@ void fapi_to_phy_translator::handle_new_slot(slot_point slot) current_slot_controller = slot_based_upper_phy_controller(slot); pdsch_pdu_repository.clear(); ul_pdu_repository.clear_slot(slot); + + // Update the logger context. + logger.set_context(slot.sfn(), slot.slot_index()); } diff --git a/lib/fapi_adaptor/phy/fapi_to_phy_translator.h b/lib/fapi_adaptor/phy/fapi_to_phy_translator.h index b136432686..3319b47220 100644 --- a/lib/fapi_adaptor/phy/fapi_to_phy_translator.h +++ b/lib/fapi_adaptor/phy/fapi_to_phy_translator.h @@ -197,7 +197,7 @@ class fapi_to_phy_translator : public fapi::slot_message_gateway /// Current slot task controller. slot_based_upper_phy_controller current_slot_controller; /// PDSCH PDU repository. - static_vector pdsch_pdu_repository; + static_vector pdsch_pdu_repository; /// Uplink slot PDU repository. uplink_slot_pdu_repository& ul_pdu_repository; /// Protects concurrent access to shared variables. diff --git a/lib/fapi_adaptor/phy/messages/pucch.cpp b/lib/fapi_adaptor/phy/messages/pucch.cpp index 047b13de6f..3c8c4ec159 100644 --- a/lib/fapi_adaptor/phy/messages/pucch.cpp +++ b/lib/fapi_adaptor/phy/messages/pucch.cpp @@ -27,7 +27,8 @@ using namespace fapi_adaptor; static void fill_format1_parameters(pucch_processor::format1_configuration& config, const fapi::ul_pucch_pdu& fapi_pdu, - slot_point slot) + slot_point slot, + uint16_t num_rx_ant) { config.slot = slot; config.bwp_size_rb = fapi_pdu.bwp_size; @@ -41,7 +42,9 @@ static void fill_format1_parameters(pucch_processor::format1_configuration& conf config.n_id = fapi_pdu.nid_pucch_hopping; config.nof_harq_ack = fapi_pdu.bit_len_harq; - config.ports = {0}; + // Fill the antenna port indices starting from 0. + config.ports.resize(num_rx_ant); + std::iota(config.ports.begin(), config.ports.end(), 0); config.initial_cyclic_shift = fapi_pdu.initial_cyclic_shift; config.nof_symbols = fapi_pdu.nr_of_symbols; @@ -54,11 +57,11 @@ static void fill_format1_parameters(pucch_processor::format1_configuration& conf static void fill_format2_parameters(pucch_processor::format2_configuration& config, const fapi::ul_pucch_pdu& fapi_pdu, - slot_point slot) + slot_point slot, + uint16_t num_rx_ant) { config.slot = slot; config.cp = fapi_pdu.cp; - config.ports = {0}; config.bwp_size_rb = fapi_pdu.bwp_size; config.bwp_start_rb = fapi_pdu.bwp_start; config.starting_prb = fapi_pdu.prb_start; @@ -81,6 +84,10 @@ static void fill_format2_parameters(pucch_processor::format2_configuration& conf // Fill PUCCH context for logging. config.context = pucch_context(fapi_pdu.rnti); + + // Fill the antenna port indices starting from 0. + config.ports.resize(num_rx_ant); + std::iota(config.ports.begin(), config.ports.end(), 0); } /// Fills the context for Format 0 and Format 1. @@ -93,7 +100,8 @@ static void fill_pucch_format_0_1_context(ul_pucch_context& context, const fapi: void srsran::fapi_adaptor::convert_pucch_fapi_to_phy(uplink_processor::pucch_pdu& pdu, const fapi::ul_pucch_pdu& fapi_pdu, uint16_t sfn, - uint16_t slot) + uint16_t slot, + uint16_t num_rx_ant) { // Fill main context fields. ul_pucch_context& context = pdu.context; @@ -107,10 +115,10 @@ void srsran::fapi_adaptor::convert_pucch_fapi_to_phy(uplink_processor::pucch_pdu switch (context.format) { case pucch_format::FORMAT_1: - fill_format1_parameters(pdu.format1, fapi_pdu, slot_point(fapi_pdu.scs, sfn, slot)); + fill_format1_parameters(pdu.format1, fapi_pdu, slot_point(fapi_pdu.scs, sfn, slot), num_rx_ant); break; case pucch_format::FORMAT_2: - fill_format2_parameters(pdu.format2, fapi_pdu, slot_point(fapi_pdu.scs, sfn, slot)); + fill_format2_parameters(pdu.format2, fapi_pdu, slot_point(fapi_pdu.scs, sfn, slot), num_rx_ant); break; default: srsran_assert(0, "Unsupported PUCCH format {}", context.format); diff --git a/lib/fapi_adaptor/uci_part2_correspondence_generator.cpp b/lib/fapi_adaptor/uci_part2_correspondence_generator.cpp new file mode 100644 index 0000000000..973cd73d39 --- /dev/null +++ b/lib/fapi_adaptor/uci_part2_correspondence_generator.cpp @@ -0,0 +1,109 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srsran/fapi_adaptor/uci_part2_correspondence_generator.h" +#include "uci_part2_mapper_functions.h" +#include "srsran/ran/csi_report/csi_report_on_pusch_helpers.h" +#include "srsran/ran/csi_report/csi_report_on_puxch_utils.h" + +using namespace srsran; +using namespace fapi_adaptor; + +/// Adds a new entry in the specified position resizing the container if needed. +template +static auto add_map_entry(T& container, unsigned index) -> typename T::reference +{ + if (index >= container.size()) { + container.resize(index + 1U); + } + + return container[index]; +} + +std::pair, std::unique_ptr> +srsran::fapi_adaptor::generate_uci_part2_correspondence(unsigned nof_csi_rs_resources) +{ + srsran_assert(nof_csi_rs_resources && nof_csi_rs_resources <= MAX_NUM_CSI_RESOURCES, + "Unsupported number of CSI-RS resources"); + + unsigned map_index = 0; + std::vector< + static_vector> + mapper_map; + std::vector> repo_map; + + // Skip generating entries with nof_csi_rs_resources set to 0. + for (unsigned csi_resource_index = 1; csi_resource_index != (nof_csi_rs_resources + 1); ++csi_resource_index) { + for (unsigned codebook_index = 0; codebook_index != MAX_NUM_CODEBOOKS; ++codebook_index) { + // The maximum value of the RI restriction field depends on the number of CSI-RS ports which is derived from the + // used codebook. + for (unsigned ri_index = 1, + nof_csi_rs_ports = + csi_report_get_nof_csi_rs_antenna_ports(static_cast(codebook_index)), + ri_index_end = pow2(nof_csi_rs_ports); + ri_index != ri_index_end; + ++ri_index) { + for (unsigned quantities_index = 0; quantities_index != MAX_NUM_QUANTITIES; ++quantities_index) { + ri_restriction_type ri_restriction(nof_csi_rs_ports); + ri_restriction.from_uint64(ri_index); + + csi_report_configuration report_cfg; + report_cfg.nof_csi_rs_resources = csi_resource_index; + report_cfg.pmi_codebook = static_cast(codebook_index); + report_cfg.ri_restriction = ri_restriction; + report_cfg.quantities = static_cast(quantities_index); + + uci_part2_size_description part2_correspondence = get_csi_report_pusch_size(report_cfg).part2_correspondence; + auto& map_entry = add_map_entry( + mapper_map, + get_uci_part2_correspondence_index(csi_resource_index, codebook_index, ri_index, quantities_index)); + + for (const auto& entry : part2_correspondence.entries) { + uci_part2_correspondence_information info; + + // Highest priority. + info.priority = 0; + + for (const auto& param : entry.parameters) { + info.part1_params.push_back({param.offset, param.width}); + } + + info.part2_map_index = map_index; + + // PHY specific context. + info.part2_map_scope = 1; + + map_entry.push_back(info); + + // Store the map of this entry in the repository. + add_map_entry(repo_map, map_index); + repo_map[map_index] = entry.map; + ++map_index; + } + } + } + } + } + + return {std::make_unique(std::move(mapper_map)), + std::make_unique(std::move(repo_map))}; +} diff --git a/lib/fapi_adaptor/uci_part2_correspondence_mapper.cpp b/lib/fapi_adaptor/uci_part2_correspondence_mapper.cpp new file mode 100644 index 0000000000..47b82a9872 --- /dev/null +++ b/lib/fapi_adaptor/uci_part2_correspondence_mapper.cpp @@ -0,0 +1,49 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srsran/fapi_adaptor/uci_part2_correspondence_mapper.h" +#include "uci_part2_mapper_functions.h" +#include "srsran/ran/csi_report/csi_report_configuration.h" + +using namespace srsran; +using namespace fapi_adaptor; + +span +uci_part2_correspondence_mapper::map(const csi_report_configuration& csi_report) const +{ + srsran_assert(csi_report.nof_csi_rs_resources && csi_report.nof_csi_rs_resources <= MAX_NUM_CSI_RESOURCES, + "Unsupported number of CSI-RS resources"); + srsran_assert(csi_report.ri_restriction.to_uint64() < MAX_NUM_RI_RESTRICTIONS, "Unsupported RI restriction value"); + + unsigned index = get_uci_part2_correspondence_index(csi_report.nof_csi_rs_resources, + static_cast(csi_report.pmi_codebook), + static_cast(csi_report.ri_restriction.to_uint64()), + static_cast(csi_report.quantities)); + + srsran_assert( + index < correspondence_map.size(), + "Invalid UCI Part2 correspondence calculated index, index value is '{}' while map size has '{}' entries", + index, + correspondence_map.size()); + + return correspondence_map[index]; +} diff --git a/lib/fapi_adaptor/uci_part2_correspondence_repository.cpp b/lib/fapi_adaptor/uci_part2_correspondence_repository.cpp new file mode 100644 index 0000000000..adc6da4489 --- /dev/null +++ b/lib/fapi_adaptor/uci_part2_correspondence_repository.cpp @@ -0,0 +1,36 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srsran/fapi_adaptor/uci_part2_correspondence_repository.h" + +using namespace srsran; +using namespace fapi_adaptor; + +span uci_part2_correspondence_repository::get_uci_part2_correspondence(unsigned index) const +{ + srsran_assert(index < repo_map.size(), + "Invalid UCI Part2 correspondence index, index value is '{}' while repository size has '{}' entries", + index, + repo_map.size()); + + return repo_map[index]; +} diff --git a/lib/fapi_adaptor/uci_part2_mapper_functions.h b/lib/fapi_adaptor/uci_part2_mapper_functions.h new file mode 100644 index 0000000000..946bbc3454 --- /dev/null +++ b/lib/fapi_adaptor/uci_part2_mapper_functions.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/ran/csi_report/csi_report_configuration.h" + +namespace srsran { +namespace fapi_adaptor { + +constexpr unsigned MAX_NUM_RI_RESTRICTIONS = (1U << 4); +constexpr unsigned MAX_NUM_CSI_RESOURCES = 1; +constexpr unsigned MAX_NUM_CODEBOOKS = static_cast(pmi_codebook_type::other); +constexpr unsigned MAX_NUM_QUANTITIES = static_cast(csi_report_quantities::other); + +/// Returns the UCI Part2 correspondence index using the given parameters. +inline unsigned get_uci_part2_correspondence_index(unsigned nof_csi_resources, + unsigned pmi_codebook, + unsigned ri_restriction, + unsigned quantities) +{ + return (MAX_NUM_RI_RESTRICTIONS * MAX_NUM_CSI_RESOURCES * MAX_NUM_CODEBOOKS * quantities) + + (MAX_NUM_CSI_RESOURCES * MAX_NUM_CODEBOOKS * ri_restriction) + (MAX_NUM_CSI_RESOURCES * pmi_codebook) + + nof_csi_resources; +} + +} // namespace fapi_adaptor +} // namespace srsran diff --git a/lib/gateways/udp_network_gateway_impl.cpp b/lib/gateways/udp_network_gateway_impl.cpp index 534c1f7eb3..512158fc7e 100644 --- a/lib/gateways/udp_network_gateway_impl.cpp +++ b/lib/gateways/udp_network_gateway_impl.cpp @@ -39,6 +39,18 @@ udp_network_gateway_impl::udp_network_gateway_impl(udp_network_gateway_config network_gateway_data_notifier_with_src_addr& data_notifier_) : config(std::move(config_)), data_notifier(data_notifier_), logger(srslog::fetch_basic_logger("UDP-GW")) { + logger.info("UDP GW configured. rx_max_mmsg={}", config.rx_max_mmsg); + + // Allocate RX buffers + rx_mem.resize(config.rx_max_mmsg); + for (uint32_t i = 0; i < config.rx_max_mmsg; ++i) { + rx_mem[i].resize(network_gateway_udp_max_len); + } + + // Allocate context for recv_mmsg + rx_srcaddr.resize(config.rx_max_mmsg); + rx_msghdr.resize(config.rx_max_mmsg); + rx_iovecs.resize(config.rx_max_mmsg); } bool udp_network_gateway_impl::is_initialized() @@ -117,7 +129,8 @@ bool udp_network_gateway_impl::create_and_bind() continue; } - char ip_addr[NI_MAXHOST], port_nr[NI_MAXSERV]; + char ip_addr[NI_MAXHOST]; + char port_nr[NI_MAXSERV]; getnameinfo( result->ai_addr, result->ai_addrlen, ip_addr, NI_MAXHOST, port_nr, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV); logger.debug("Binding to {} port {}", ip_addr, port_nr); @@ -176,24 +189,40 @@ void udp_network_gateway_impl::receive() logger.error("Cannot receive on UDP gateway: Socket is not initialized."); } - // Fixme: consider class member on heap when sequential access is guaranteed - std::array tmp_mem; // no init + socklen_t src_addr_len = sizeof(struct sockaddr_storage); + + for (unsigned i = 0; i < config.rx_max_mmsg; ++i) { + rx_msghdr[i].msg_hdr = {}; + rx_msghdr[i].msg_hdr.msg_name = &rx_srcaddr[i]; + rx_msghdr[i].msg_hdr.msg_namelen = src_addr_len; - sockaddr_storage src_addr = {}; - socklen_t src_addr_len = sizeof(struct sockaddr_storage); - int rx_bytes = recvfrom(sock_fd, tmp_mem.data(), network_gateway_udp_max_len, 0, (sockaddr*)&src_addr, &src_addr_len); + rx_iovecs[i].iov_base = rx_mem[i].data(); + rx_iovecs[i].iov_len = network_gateway_udp_max_len; + rx_msghdr[i].msg_hdr.msg_iov = &rx_iovecs[i]; + rx_msghdr[i].msg_hdr.msg_iovlen = 1; + } - if (rx_bytes == -1 && errno != EAGAIN) { + int rx_msgs = recvmmsg(sock_fd, rx_msghdr.data(), config.rx_max_mmsg, MSG_WAITFORONE, nullptr); + if (rx_msgs == -1 && errno != EAGAIN) { logger.error("Error reading from UDP socket: {}", strerror(errno)); - } else if (rx_bytes == -1 && errno == EAGAIN) { + return; + } + if (rx_msgs == -1 && errno == EAGAIN) { if (!config.non_blocking_mode) { logger.debug("Socket timeout reached"); } - } else { - logger.debug("Received {} bytes on UDP socket", rx_bytes); - span payload(tmp_mem.data(), rx_bytes); - byte_buffer pdu = {payload}; - data_notifier.on_new_pdu(std::move(pdu), src_addr); + return; + } + + for (int i = 0; i < rx_msgs; ++i) { + span payload(rx_mem[i].data(), rx_msghdr[i].msg_len); + byte_buffer pdu = {}; + if (pdu.append(payload)) { + logger.debug("Received {} bytes on UDP socket", rx_msghdr[i].msg_len); + data_notifier.on_new_pdu(std::move(pdu), *(sockaddr_storage*)rx_msghdr[i].msg_hdr.msg_name); + } else { + logger.error("Could not allocate byte buffer. Received {} bytes on UDP socket", rx_msghdr[i].msg_len); + } } } diff --git a/lib/gateways/udp_network_gateway_impl.h b/lib/gateways/udp_network_gateway_impl.h index 189d9a9eaf..47b574e4ea 100644 --- a/lib/gateways/udp_network_gateway_impl.h +++ b/lib/gateways/udp_network_gateway_impl.h @@ -70,6 +70,12 @@ class udp_network_gateway_impl final : public udp_network_gateway int local_ai_family = 0; int local_ai_socktype = 0; int local_ai_protocol = 0; + + /// Temporary RX buffers for reception. + std::vector> rx_mem; + std::vector<::sockaddr_storage> rx_srcaddr; + std::vector<::mmsghdr> rx_msghdr; + std::vector<::iovec> rx_iovecs; }; } // namespace srsran diff --git a/lib/gtpu/gtpu_demux_impl.cpp b/lib/gtpu/gtpu_demux_impl.cpp index 15a0774533..346b972289 100644 --- a/lib/gtpu/gtpu_demux_impl.cpp +++ b/lib/gtpu/gtpu_demux_impl.cpp @@ -77,7 +77,7 @@ void gtpu_demux_impl::handle_pdu_impl(gtpu_teid_t teid, byte_buffer pdu, const s const auto& it = teid_to_tunnel.find(teid); if (it == teid_to_tunnel.end()) { - logger.error("Dropped GTP-U PDU, tunnel not found. teid={}", teid); + logger.info("Dropped GTP-U PDU, tunnel not found. teid={}", teid); return; } logger.debug(pdu.begin(), pdu.end(), "Forwarding PDU. pdu_len={} teid={}", pdu.length(), teid); diff --git a/lib/mac/mac_config_interfaces.h b/lib/mac/mac_config_interfaces.h index d19ee0cfde..18917356dc 100644 --- a/lib/mac/mac_config_interfaces.h +++ b/lib/mac/mac_config_interfaces.h @@ -36,7 +36,7 @@ class mac_ul_configurator const std::vector& ul_logical_channels) = 0; virtual async_task remove_bearers(du_ue_index_t ue_index, span lcids_to_rem) = 0; virtual async_task remove_ue(const mac_ue_delete_request& msg) = 0; - virtual void flush_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) = 0; + virtual bool flush_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) = 0; }; class mac_dl_configurator : public mac_cell_manager diff --git a/lib/mac/mac_ctrl/mac_controller.h b/lib/mac/mac_ctrl/mac_controller.h index 15573aca33..e8c6385ea8 100644 --- a/lib/mac/mac_ctrl/mac_controller.h +++ b/lib/mac/mac_ctrl/mac_controller.h @@ -66,9 +66,9 @@ class mac_controller : public mac_ctrl_configurator, public mac_ue_configurator, async_task handle_ue_reconfiguration_request(const mac_ue_reconfiguration_request& msg) override; - void handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override + bool handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override { - ul_unit.flush_ul_ccch_msg(ue_index, std::move(pdu)); + return ul_unit.flush_ul_ccch_msg(ue_index, std::move(pdu)); } /// Fetch UE context diff --git a/lib/mac/mac_dl/dl_sch_pdu_assembler.cpp b/lib/mac/mac_dl/dl_sch_pdu_assembler.cpp index c5192f3915..a9cd9c3041 100644 --- a/lib/mac/mac_dl/dl_sch_pdu_assembler.cpp +++ b/lib/mac/mac_dl/dl_sch_pdu_assembler.cpp @@ -50,8 +50,12 @@ unsigned dl_sch_pdu::add_sdu(lcid_t lcid_, byte_buffer_chain&& sdu) encode_subheader(F_bit, lcid, header_length, sdu_len); // Encode Payload. - std::copy(sdu.begin(), sdu.end(), pdu.data() + byte_offset); - byte_offset += sdu_len; + for (const byte_buffer_slice& sl : sdu.slices()) { + for (const span seg : sl.segments()) { + std::copy(seg.begin(), seg.end(), pdu.data() + byte_offset); + byte_offset += seg.size(); + } + } return sdu_len + header_length; } diff --git a/lib/mac/mac_dl/mac_cell_processor.cpp b/lib/mac/mac_dl/mac_cell_processor.cpp index ac5ff75347..85b3a7af2d 100644 --- a/lib/mac/mac_dl/mac_cell_processor.cpp +++ b/lib/mac/mac_dl/mac_cell_processor.cpp @@ -51,7 +51,7 @@ mac_cell_processor::mac_cell_processor(const mac_cell_creation_request& cell_cfg MAX_K0_DELAY, get_nof_slots_per_subframe(cell_cfg.scs_common) * NOF_SFNS * NOF_SUBFRAMES_PER_FRAME), ssb_helper(cell_cfg_req_), - sib_assembler(cell_cfg_req_.bcch_dl_sch_payload), + sib_assembler(cell_cfg_req_.bcch_dl_sch_payloads), rar_assembler(pdu_pool), dlsch_assembler(ue_mng_), paging_assembler(pdu_pool), @@ -246,7 +246,13 @@ void mac_cell_processor::assemble_dl_data_request(mac_dl_data_result& data_re // Assemble scheduled BCCH-DL-SCH message containing SIBs' payload. for (const sib_information& sib_info : dl_res.bc.sibs) { srsran_assert(not data_res.sib1_pdus.full(), "No SIB1 added as SIB1 list in MAC DL data results is already full"); - span payload = sib_assembler.encode_sib_pdu(sib_info.pdsch_cfg.codewords[0].tb_size_bytes); + const units::bytes tbs(sib_info.pdsch_cfg.codewords[0].tb_size_bytes); + span payload; + if (sib_info.si_indicator == sib_information::sib1) { + payload = sib_assembler.encode_sib1_pdu(tbs); + } else { + payload = sib_assembler.encode_si_message_pdu(sib_info.si_msg_index.value(), tbs); + } data_res.sib1_pdus.emplace_back(0, payload); } diff --git a/lib/mac/mac_dl/mac_dl_processor.cpp b/lib/mac/mac_dl/mac_dl_processor.cpp index 1169fbaba7..ea8ce1126b 100644 --- a/lib/mac/mac_dl/mac_dl_processor.cpp +++ b/lib/mac/mac_dl/mac_dl_processor.cpp @@ -29,7 +29,7 @@ using namespace srsran; mac_dl_processor::mac_dl_processor(const mac_dl_config& mac_cfg, mac_scheduler_cell_info_handler& sched_, du_rnti_table& rnti_table_) : - cfg(mac_cfg), ue_mng(rnti_table_, cfg.rlf_handler), sched(sched_) + cfg(mac_cfg), ue_mng(rnti_table_), sched(sched_) { } @@ -66,20 +66,16 @@ async_task mac_dl_processor::add_ue(const mac_ue_create_request& request) { // > Allocate UE DL HARQ buffers. // Note: This is a large allocation, and therefore, should be done outside of the cell thread to avoid causing lates. - std::vector> harq_buffers; - harq_buffers.resize(MAX_NOF_HARQS); - for (auto& h : harq_buffers) { - h.resize(MAX_DL_PDU_LENGTH); - } + mac_dl_ue_context ue_inst(request); - return launch_async([this, request, harq_buffers = std::move(harq_buffers)](coro_context>& ctx) { + return launch_async([this, request, ue_inst = std::move(ue_inst)](coro_context>& ctx) mutable { CORO_BEGIN(ctx); // > Change to respective DL executor CORO_AWAIT(execute_on(cfg.cell_exec_mapper.executor(request.cell_index))); // > Insert UE and DL bearers - ue_mng.add_ue(request, std::move(harq_buffers)); + ue_mng.add_ue(std::move(ue_inst)); // > Change back to CTRL executor before returning CORO_AWAIT(execute_on(cfg.ctrl_exec)); diff --git a/lib/mac/mac_dl/mac_dl_processor.h b/lib/mac/mac_dl/mac_dl_processor.h index 5b89ce049e..5ea24e7b13 100644 --- a/lib/mac/mac_dl/mac_dl_processor.h +++ b/lib/mac/mac_dl/mac_dl_processor.h @@ -38,7 +38,6 @@ struct mac_dl_config { task_executor& ctrl_exec; mac_result_notifier& phy_notifier; mac_pcap& pcap; - rlf_detector& rlf_handler; }; class mac_dl_processor final : public mac_dl_configurator diff --git a/lib/mac/mac_dl/mac_dl_ue_manager.cpp b/lib/mac/mac_dl/mac_dl_ue_manager.cpp index 8f9ecf9d49..56990d7534 100644 --- a/lib/mac/mac_dl/mac_dl_ue_manager.cpp +++ b/lib/mac/mac_dl/mac_dl_ue_manager.cpp @@ -21,105 +21,110 @@ */ #include "mac_dl_ue_manager.h" +#include "srsran/ran/pdsch/pdsch_constants.h" #include "srsran/support/timers.h" using namespace srsran; -mac_dl_ue_manager::mac_dl_ue_manager(du_rnti_table& rnti_table_, rlf_detector& rlf_handler_) : - rnti_table(rnti_table_), rlf_handler(rlf_handler_) +mac_dl_ue_context::mac_dl_ue_context(const mac_ue_create_request& req) : + ue_index(req.ue_index), harq_buffers(MAX_NOF_HARQS) { + // Resize each HARQ buffer to maximum DL PDU size. + // TODO: Account the maximum PDU length, given cell BW. + for (std::vector& harq : harq_buffers) { + harq.resize(MAX_DL_PDU_LENGTH); + } + + // Store DL logical channel notifiers. + addmod_logical_channels(req.bearers); + + // Store UL-CCCH + if (req.ul_ccch_msg != nullptr) { + // If the Msg3 contained an UL-CCCH message, store it for Contention Resolution. + srsran_assert(req.ul_ccch_msg->length() >= UE_CON_RES_ID_LEN, + "Invalid UL-CCCH message length ({} < 6)", + req.ul_ccch_msg->length()); + std::copy(req.ul_ccch_msg->begin(), req.ul_ccch_msg->begin() + UE_CON_RES_ID_LEN, msg3_subpdu.begin()); + } } -bool mac_dl_ue_manager::add_ue(const mac_ue_create_request& request, std::vector> dl_harq_buffers) +void mac_dl_ue_context::addmod_logical_channels(span dl_logical_channels) { - std::lock_guard lock(ue_mutex[request.ue_index]); - return add_ue_nolock(request.ue_index, - request.crnti, - request.ul_ccch_msg, - std::move(dl_harq_buffers), - *request.rlf_notifier) and - addmod_bearers_nolock(request.ue_index, request.bearers); + for (const mac_logical_channel_config& lc : dl_logical_channels) { + dl_bearers.insert(lc.lcid, lc.dl_bearer); + } } -bool mac_dl_ue_manager::remove_ue(du_ue_index_t ue_index) +void mac_dl_ue_context::remove_logical_channels(span lcids_to_remove) { - // Remove UE from RLF detector. - rlf_handler.rem_ue(ue_index); - - std::lock_guard lock(ue_mutex[ue_index]); - if (not ue_db.contains(ue_index)) { - return false; + for (const lcid_t lcid : lcids_to_remove) { + dl_bearers.erase(lcid); } - ue_db.erase(ue_index); - - return true; } -bool mac_dl_ue_manager::addmod_bearers(du_ue_index_t ue_index, - span dl_logical_channels) +// /////////////////////// + +mac_dl_ue_manager::mac_dl_ue_manager(du_rnti_table& rnti_table_) : rnti_table(rnti_table_) {} + +bool mac_dl_ue_manager::add_ue(mac_dl_ue_context ue_to_add) { - std::lock_guard lock(ue_mutex[ue_index]); - return addmod_bearers_nolock(ue_index, dl_logical_channels); + const du_ue_index_t ue_index = ue_to_add.get_ue_index(); + + // Register the UE in the repository. + { + const std::lock_guard lock(ue_mutex[ue_index]); + if (ue_db.contains(ue_index)) { + return false; + } + ue_db.emplace(ue_index, std::move(ue_to_add)); + } + + return true; } -bool mac_dl_ue_manager::remove_bearers(du_ue_index_t ue_index, span lcids) +bool mac_dl_ue_manager::remove_ue(du_ue_index_t ue_index) { - std::lock_guard lock(ue_mutex[ue_index]); + // Erase UE from the repository. + const std::lock_guard lock(ue_mutex[ue_index]); if (not ue_db.contains(ue_index)) { return false; } - auto& u = ue_db[ue_index]; - for (lcid_t lcid : lcids) { - u.dl_bearers.erase(lcid); - } + ue_db.erase(ue_index); + return true; } -bool mac_dl_ue_manager::add_ue_nolock(du_ue_index_t ue_index, - rnti_t crnti, - const byte_buffer* ul_ccch_msg, - std::vector> dl_harq_buffers, - mac_ue_radio_link_notifier& rlf_notifier) +bool mac_dl_ue_manager::addmod_bearers(du_ue_index_t ue_index, + span dl_logical_channels) { - if (ue_db.contains(ue_index)) { + const std::lock_guard lock(ue_mutex[ue_index]); + if (not ue_db.contains(ue_index)) { return false; } + auto& u = ue_db[ue_index]; - ue_db.emplace(ue_index, ue_index, crnti); - auto& u = ue_db[ue_index]; - u.harq_buffers = std::move(dl_harq_buffers); - if (ul_ccch_msg != nullptr) { - // Store Msg3. - srsran_assert( - ul_ccch_msg->length() >= UE_CON_RES_ID_LEN, "Invalid UL-CCCH message length ({} < 6)", ul_ccch_msg->length()); - std::copy(ul_ccch_msg->begin(), ul_ccch_msg->begin() + UE_CON_RES_ID_LEN, u.msg3_subpdu.begin()); - } - - // Register UE in RLF detector. - rlf_handler.add_ue(ue_index, rlf_notifier); - + u.addmod_logical_channels(dl_logical_channels); return true; } -bool mac_dl_ue_manager::addmod_bearers_nolock(du_ue_index_t ue_index, - span dl_logical_channels) +bool mac_dl_ue_manager::remove_bearers(du_ue_index_t ue_index, span lcids) { + const std::lock_guard lock(ue_mutex[ue_index]); if (not ue_db.contains(ue_index)) { return false; } auto& u = ue_db[ue_index]; - for (const mac_logical_channel_config& lc : dl_logical_channels) { - u.dl_bearers.insert(lc.lcid, lc.dl_bearer); - } + + u.remove_logical_channels(lcids); return true; } ue_con_res_id_t mac_dl_ue_manager::get_con_res_id(rnti_t rnti) { - du_ue_index_t ue_index = rnti_table[rnti]; - std::lock_guard lock(ue_mutex[ue_index]); + const du_ue_index_t ue_index = rnti_table[rnti]; + const std::lock_guard lock(ue_mutex[ue_index]); if (not ue_db.contains(ue_index)) { return {}; } - return ue_db[ue_index].msg3_subpdu; + return ue_db[ue_index].get_con_res_id(); } diff --git a/lib/mac/mac_dl/mac_dl_ue_manager.h b/lib/mac/mac_dl/mac_dl_ue_manager.h index f902714a40..2c369169f6 100644 --- a/lib/mac/mac_dl/mac_dl_ue_manager.h +++ b/lib/mac/mac_dl/mac_dl_ue_manager.h @@ -22,7 +22,6 @@ #pragma once -#include "rlf_detector.h" #include "srsran/du_high/rnti_value_table.h" #include "srsran/mac/mac.h" #include "srsran/mac/mac_config.h" @@ -33,22 +32,56 @@ namespace srsran { +// Array of bytes used to store the UE Contention Resolution Id. constexpr static size_t UE_CON_RES_ID_LEN = 6; using ue_con_res_id_t = std::array; +// Table for conversion between RNTI and ue indexes. using du_rnti_table = rnti_value_table; +/// Context of a UE in the MAC DL. +class mac_dl_ue_context +{ +public: + explicit mac_dl_ue_context(const mac_ue_create_request& req); + // expensive copy due to HARQ buffers. Cheap move. + mac_dl_ue_context(const mac_dl_ue_context&) = delete; + mac_dl_ue_context(mac_dl_ue_context&&) noexcept = default; + mac_dl_ue_context& operator=(const mac_dl_ue_context&) = delete; + mac_dl_ue_context& operator=(mac_dl_ue_context&&) noexcept = default; + + du_ue_index_t get_ue_index() const { return ue_index; } + + // HARQ buffer methods. + span dl_harq_buffer(harq_id_t h_id) { return harq_buffers[h_id]; } + span dl_harq_buffer(harq_id_t h_id) const { return harq_buffers[h_id]; } + + // DL Logical Channel methods. + const slotted_id_vector& logical_channels() const { return dl_bearers; } + void addmod_logical_channels(span dl_logical_channels); + void remove_logical_channels(span lcids_to_remove); + + const ue_con_res_id_t& get_con_res_id() const { return msg3_subpdu; } + +private: + du_ue_index_t ue_index; + std::vector> harq_buffers; + slotted_id_vector dl_bearers; + ue_con_res_id_t msg3_subpdu = {}; +}; + +/// Repository of UE MAC DL contexts. class mac_dl_ue_manager { public: - mac_dl_ue_manager(du_rnti_table& rnti_table_, rlf_detector& rlf_handler_); + mac_dl_ue_manager(du_rnti_table& rnti_table_); /// Check if UE with provided C-RNTI exists. /// \param rnti C-RNTI of the UE. /// \return True if UE exists. False, otherwise. bool contains_rnti(rnti_t rnti) const { - du_ue_index_t ue_index = rnti_table[rnti]; + const du_ue_index_t ue_index = rnti_table[rnti]; if (is_du_ue_index_valid(ue_index)) { std::lock_guard lock(ue_mutex[ue_index]); return ue_db.contains(ue_index); @@ -56,13 +89,13 @@ class mac_dl_ue_manager return false; } - /// Checks if bearer with provided C-RNTI and LCID exists. + /// Checks if Logical Channel with provided C-RNTI and LCID exists. bool contains_lcid(rnti_t rnti, lcid_t lcid) const { - du_ue_index_t ue_index = rnti_table[rnti]; + const du_ue_index_t ue_index = rnti_table[rnti]; if (is_du_ue_index_valid(ue_index)) { std::lock_guard lock(ue_mutex[ue_index]); - return ue_db.contains(ue_index) and ue_db[ue_index].dl_bearers.contains(lcid); + return ue_db.contains(ue_index) and ue_db[ue_index].logical_channels().contains(lcid); } return false; } @@ -86,11 +119,11 @@ class mac_dl_ue_manager if (not ue_db.contains(ue_index)) { return nullptr; } - ue_item& u = ue_db[ue_index]; - return u.dl_bearers.contains(lcid) ? u.dl_bearers[lcid] : nullptr; + auto& u = ue_db[ue_index]; + return u.logical_channels().contains(lcid) ? u.logical_channels()[lcid] : nullptr; } - bool add_ue(const mac_ue_create_request& request, std::vector> dl_harq_buffers); + bool add_ue(mac_dl_ue_context ue_to_add); bool remove_ue(du_ue_index_t ue_index); @@ -111,40 +144,15 @@ class mac_dl_ue_manager if (not ue_db.contains(ue_index)) { return {}; } - return ue_db[ue_index].harq_buffers[h_id]; + return ue_db[ue_index].dl_harq_buffer(h_id); } - /// \brief Handle received UE CRC. - void report_crc(du_ue_index_t ue_index, bool crc) { rlf_handler.handle_crc(ue_index, crc); } - - /// \brief Handle received UE HARQ-ACK. - void report_ack(du_ue_index_t ue_index, bool ack) { rlf_handler.handle_ack(ue_index, ack); } - private: - struct ue_item { - du_ue_index_t ue_index = MAX_NOF_DU_UES; - rnti_t rnti = INVALID_RNTI; - slotted_vector dl_bearers; - ue_con_res_id_t msg3_subpdu; - std::vector> harq_buffers; - - explicit ue_item(du_ue_index_t ue_index_, rnti_t rnti_) : ue_index(ue_index_), rnti(rnti_) {} - }; - - bool add_ue_nolock(du_ue_index_t ue_index, - rnti_t crnti, - const byte_buffer* ul_ccch_msg, - std::vector> dl_harq_buffers, - mac_ue_radio_link_notifier& rlf_notifier); - - bool addmod_bearers_nolock(du_ue_index_t ue_index, span dl_logical_channels); - du_rnti_table& rnti_table; - rlf_detector& rlf_handler; mutable std::array ue_mutex; - du_ue_list ue_db; + du_ue_list ue_db; }; } // namespace srsran diff --git a/lib/mac/mac_dl/sib_pdu_assembler.h b/lib/mac/mac_dl/sib_pdu_assembler.h index a4d0e7dc69..625fddd8c3 100644 --- a/lib/mac/mac_dl/sib_pdu_assembler.h +++ b/lib/mac/mac_dl/sib_pdu_assembler.h @@ -31,29 +31,46 @@ namespace srsran { class sib_pdu_assembler { public: - explicit sib_pdu_assembler(const byte_buffer& bcch_dl_sch_payload) : min_payload_size(bcch_dl_sch_payload.length()) + explicit sib_pdu_assembler(const std::vector& bcch_dl_sch_payloads) { - // Number of padding bytes to prereserve. This value is implementation-defined. + // Number of padding bytes to pre-reserve. This value is implementation-defined. static constexpr unsigned MAX_PADDING_BYTES_LEN = 64; - // Note: Resizing the bcch_payload after the ctor is forbidden, to avoid vector memory relocations and invalidation - // of pointers passed to the lower layers. For this reason, we pre-reserve any potential padding bytes. - bcch_payload.resize(min_payload_size + MAX_PADDING_BYTES_LEN, 0); - bcch_payload.assign(bcch_dl_sch_payload.begin(), bcch_dl_sch_payload.end()); + + bcch_min_payload_sizes.resize(bcch_dl_sch_payloads.size()); + bcch_payloads.resize(bcch_dl_sch_payloads.size()); + for (unsigned i = 0; i != bcch_payloads.size(); ++i) { + bcch_min_payload_sizes[i] = units::bytes(bcch_dl_sch_payloads[i].length()); + + // Note: Resizing the bcch_payload after the ctor is forbidden, to avoid vector memory relocations and + // invalidation of pointers passed to the lower layers. For this reason, we pre-reserve any potential padding + // bytes. + bcch_payloads[i].resize(bcch_dl_sch_payloads[i].length() + MAX_PADDING_BYTES_LEN, 0); + std::copy(bcch_dl_sch_payloads[i].begin(), bcch_dl_sch_payloads[i].end(), bcch_payloads[i].begin()); + } } - span encode_sib_pdu(unsigned tbs_bytes) const + span encode_sib1_pdu(units::bytes tbs_bytes) const { return encode_si_pdu(0, tbs_bytes); } + + span encode_si_message_pdu(unsigned si_msg_idx, units::bytes tbs_bytes) const { - srsran_assert(tbs_bytes >= min_payload_size, "The TBS for SIB1 cannot be smaller than the SIB1 payload"); - srsran_assert(tbs_bytes <= bcch_payload.capacity(), - "Memory rellocations of the SIB1 payload not allowed. Consider reserving more bytes for PADDING"); - return span(bcch_payload.data(), tbs_bytes); + return encode_si_pdu(si_msg_idx + 1, tbs_bytes); } private: - /// Holds the original BCCH-DL-SCH message, defined in the MAC cell configuration, plus extra padding bytes. - std::vector bcch_payload; + span encode_si_pdu(unsigned idx, units::bytes tbs_bytes) const + { + srsran_assert(tbs_bytes >= bcch_min_payload_sizes[idx], + "The allocated PDSCH TBS cannot be smaller than the respective SI{} payload", + idx == 0 ? fmt::format("B1") : fmt::format("-message {}", idx + 1)); + srsran_assert(tbs_bytes <= units::bytes(bcch_payloads[idx].size()), + "Memory rellocations of the SIB1 payload not allowed. Consider reserving more bytes for PADDING"); + return span(bcch_payloads[idx].data(), tbs_bytes.value()); + } + + /// Holds the original BCCH-DL-SCH messages, defined in the MAC cell configuration, plus extra padding bytes. + std::vector> bcch_payloads; /// Length of the original BCCH-DL-SCH message, without padding, defined in the MAC cell configuration. - unsigned min_payload_size; + std::vector bcch_min_payload_sizes; }; } // namespace srsran diff --git a/lib/mac/mac_impl.cpp b/lib/mac/mac_impl.cpp index 56c93cf16a..1818debe13 100644 --- a/lib/mac/mac_impl.cpp +++ b/lib/mac/mac_impl.cpp @@ -26,16 +26,11 @@ namespace srsran { mac_impl::mac_impl(const mac_config& params) : - rlf_handler(params.mac_cfg.max_consecutive_dl_kos, params.mac_cfg.max_consecutive_ul_kos), - mac_sched(std::make_unique(params, rnti_table, rlf_handler)), - dl_unit(mac_dl_config{params.ue_exec_mapper, - params.cell_exec_mapper, - params.ctrl_exec, - params.phy_notifier, - params.pcap, - rlf_handler}, - *mac_sched, - rnti_table), + mac_sched(std::make_unique(params, rnti_table)), + dl_unit( + mac_dl_config{params.ue_exec_mapper, params.cell_exec_mapper, params.ctrl_exec, params.phy_notifier, params.pcap}, + *mac_sched, + rnti_table), ul_unit(mac_ul_config{params.ctrl_exec, params.ue_exec_mapper, params.ul_ccch_notifier, diff --git a/lib/mac/mac_impl.h b/lib/mac/mac_impl.h index c1e1b955cf..3c9edbcca2 100644 --- a/lib/mac/mac_impl.h +++ b/lib/mac/mac_impl.h @@ -25,8 +25,8 @@ #include "mac_ctrl/mac_config.h" #include "mac_ctrl/mac_controller.h" #include "mac_dl/mac_dl_processor.h" -#include "mac_dl/rlf_detector.h" #include "mac_sched/mac_scheduler_adapter.h" +#include "mac_sched/rlf_detector.h" #include "mac_ul/mac_ul_processor.h" #include "rnti_manager.h" #include "srsran/mac/mac.h" @@ -68,9 +68,6 @@ class mac_impl : public mac_interface /// Used to allocate new TC-RNTIs and convert from C-RNTI to UE index. rnti_manager rnti_table; - /// Detector of UE RLFs. - rlf_detector rlf_handler; - /// MAC scheduler. std::unique_ptr mac_sched; diff --git a/lib/mac/mac_dl/rlf_detector.h b/lib/mac/mac_sched/rlf_detector.h similarity index 86% rename from lib/mac/mac_dl/rlf_detector.h rename to lib/mac/mac_sched/rlf_detector.h index 217817c68b..d0e3130f8e 100644 --- a/lib/mac/mac_dl/rlf_detector.h +++ b/lib/mac/mac_sched/rlf_detector.h @@ -63,6 +63,16 @@ class rlf_detector void handle_crc(du_ue_index_t ue_index, bool crc) { handle_ack_common(ue_index, crc, 1); } + void handle_crnti_ce(du_ue_index_t ue_index) + { + ues[ue_index].ko_counters[0] = 0; + ues[ue_index].ko_counters[1] = 0; + std::lock_guard lock(ues[ue_index].notifier_mutex); + if (ues[ue_index].notifier != nullptr) { + ues[ue_index].notifier->on_crnti_ce_received(); + } + } + private: void handle_ack_common(du_ue_index_t ue_index, bool ack, unsigned count_index) { @@ -79,17 +89,11 @@ class rlf_detector if (u.notifier != nullptr) { logger.info("ue={}: RLF detected. Cause: {} consecutive {} KOs.", ue_index, - max_consecutive_kos[count_index], + current_count, count_index == 0 ? "HARQ-ACK" : "CRC"); // Notify upper layers. - if (u.notifier->on_rlf_detected()) { - // Clear notifier to avoid sending duplicate RLF notifications. - // E.g. Take for instance the case that an RLF is triggered, the counter is reset and reaches the - // threshold again before the first RLF is handled. The second RLF would be for a UE that is going to be - // or is already removed. - u.notifier = nullptr; - } + u.notifier->on_rlf_detected(); } } } @@ -101,8 +105,9 @@ class rlf_detector struct ue_context { std::array, 2> ko_counters{}; - std::mutex notifier_mutex; mac_ue_radio_link_notifier* notifier = nullptr; + // Access to the notifier is protected, as many threads may access it. + std::mutex notifier_mutex; }; std::array ues; diff --git a/lib/mac/mac_sched/srsran_scheduler_adapter.cpp b/lib/mac/mac_sched/srsran_scheduler_adapter.cpp index 7f41fae327..fd10d5dcf5 100644 --- a/lib/mac/mac_sched/srsran_scheduler_adapter.cpp +++ b/lib/mac/mac_sched/srsran_scheduler_adapter.cpp @@ -49,11 +49,9 @@ make_scheduler_ue_reconfiguration_request(const mac_ue_reconfiguration_request& return ret; } -srsran_scheduler_adapter::srsran_scheduler_adapter(const mac_config& params, - rnti_manager& rnti_mng_, - rlf_detector& rlf_handler_) : +srsran_scheduler_adapter::srsran_scheduler_adapter(const mac_config& params, rnti_manager& rnti_mng_) : rnti_mng(rnti_mng_), - rlf_handler(rlf_handler_), + rlf_handler(params.mac_cfg.max_consecutive_dl_kos, params.mac_cfg.max_consecutive_ul_kos), ctrl_exec(params.ctrl_exec), logger(srslog::fetch_basic_logger("MAC")), notifier(*this), @@ -75,6 +73,9 @@ async_task srsran_scheduler_adapter::handle_ue_creation_request(const mac_ return launch_async([this, msg](coro_context>& ctx) { CORO_BEGIN(ctx); + // Add UE to RLF handler. + rlf_handler.add_ue(msg.ue_index, *msg.rlf_notifier); + // Create UE in the Scheduler. sched_impl->handle_ue_creation_request(make_scheduler_ue_creation_request(msg)); @@ -113,6 +114,10 @@ async_task srsran_scheduler_adapter::handle_ue_removal_request(const mac_u // Await Scheduler notification. CORO_AWAIT(sched_cfg_notif_map[msg.ue_index].ue_config_ready); sched_cfg_notif_map[msg.ue_index].ue_config_ready.reset(); + + // Remove UE from RLF handler. + rlf_handler.rem_ue(msg.ue_index); + CORO_RETURN(true); }); } @@ -195,6 +200,11 @@ void srsran_scheduler_adapter::handle_ul_phr_indication(const mac_phr_ce_info& p sched_impl->handle_ul_phr_indication(ind); } +void srsran_scheduler_adapter::handle_crnti_ce_indication(du_ue_index_t old_ue_index) +{ + rlf_handler.handle_crnti_ce(old_ue_index); +} + const sched_result& srsran_scheduler_adapter::slot_indication(slot_point slot_tx, du_cell_index_t cell_idx) { const sched_result& res = sched_impl->slot_indication(slot_tx, cell_idx); diff --git a/lib/mac/mac_sched/srsran_scheduler_adapter.h b/lib/mac/mac_sched/srsran_scheduler_adapter.h index f48999a140..cac8f40299 100644 --- a/lib/mac/mac_sched/srsran_scheduler_adapter.h +++ b/lib/mac/mac_sched/srsran_scheduler_adapter.h @@ -24,9 +24,9 @@ #include "../mac_ctrl/mac_config.h" #include "../mac_ctrl/mac_scheduler_configurator.h" -#include "../mac_dl/rlf_detector.h" #include "../rnti_manager.h" #include "mac_scheduler_adapter.h" +#include "rlf_detector.h" #include "uci_cell_decoder.h" #include "srsran/scheduler/mac_scheduler.h" #include "srsran/support/async/manual_event.h" @@ -40,7 +40,7 @@ namespace srsran { class srsran_scheduler_adapter final : public mac_scheduler_adapter { public: - explicit srsran_scheduler_adapter(const mac_config& params, rnti_manager& rnti_mng_, rlf_detector& rlf_handler_); + explicit srsran_scheduler_adapter(const mac_config& params, rnti_manager& rnti_mng_); void add_cell(const mac_cell_creation_request& msg) override; @@ -66,6 +66,8 @@ class srsran_scheduler_adapter final : public mac_scheduler_adapter void handle_ul_phr_indication(const mac_phr_ce_info& phr) override; + void handle_crnti_ce_indication(du_ue_index_t old_ue_index) override; + void handle_paging_information(const paging_information& msg) override; const sched_result& slot_indication(slot_point slot_tx, du_cell_index_t cell_idx) override; @@ -115,8 +117,9 @@ class srsran_scheduler_adapter final : public mac_scheduler_adapter srsran_scheduler_adapter& parent; }; - rnti_manager& rnti_mng; - rlf_detector& rlf_handler; + rnti_manager& rnti_mng; + /// Detector of UE RLFs. + rlf_detector rlf_handler; task_executor& ctrl_exec; srslog::basic_logger& logger; diff --git a/lib/mac/mac_sched/uci_cell_decoder.h b/lib/mac/mac_sched/uci_cell_decoder.h index fb16beca4b..801935713e 100644 --- a/lib/mac/mac_sched/uci_cell_decoder.h +++ b/lib/mac/mac_sched/uci_cell_decoder.h @@ -22,7 +22,7 @@ #pragma once -#include "../mac_dl/rlf_detector.h" +#include "rlf_detector.h" #include "srsran/du_high/rnti_value_table.h" #include "srsran/mac/mac_cell_control_information_handler.h" #include "srsran/ran/csi_report/csi_report_configuration.h" diff --git a/lib/mac/mac_ul/mac_scheduler_ce_info_handler.h b/lib/mac/mac_ul/mac_scheduler_ce_info_handler.h index 6622133ada..3ce5842b2d 100644 --- a/lib/mac/mac_ul/mac_scheduler_ce_info_handler.h +++ b/lib/mac/mac_ul/mac_scheduler_ce_info_handler.h @@ -81,6 +81,9 @@ class mac_scheduler_ce_info_handler /// \brief Forward to scheduler any decoded UL PHRs for a given UE. virtual void handle_ul_phr_indication(const mac_phr_ce_info& phr) = 0; + + /// \brief Forward to scheduler any notification of a received MAC CRNTI CE. + virtual void handle_crnti_ce_indication(du_ue_index_t old_ue_index) = 0; }; } // namespace srsran diff --git a/lib/mac/mac_ul/mac_ul_processor.h b/lib/mac/mac_ul/mac_ul_processor.h index 42c03b3b5e..9f0577887c 100644 --- a/lib/mac/mac_ul/mac_ul_processor.h +++ b/lib/mac/mac_ul/mac_ul_processor.h @@ -56,7 +56,8 @@ class mac_ul_processor final : public mac_ul_configurator, public mac_pdu_handle async_task add_ue(const mac_ue_create_request& request) override { // Update UE executor to match new PCell. - task_executor& ul_exec = cfg.ue_exec_mapper.rebind_executor(request.ue_index, request.cell_index); + cfg.ue_exec_mapper.rebind_executors(request.ue_index, request.cell_index); + task_executor& ul_exec = cfg.ue_exec_mapper.ctrl_executor(request.ue_index); // Dispatch UE creation task to new UL executor. return dispatch_and_resume_on(ul_exec, cfg.ctrl_exec, [this, request]() { return ue_manager.add_ue(request); }); @@ -66,7 +67,7 @@ class mac_ul_processor final : public mac_ul_configurator, public mac_pdu_handle const std::vector& ul_logical_channels) override { return dispatch_and_resume_on( - cfg.ue_exec_mapper.executor(ue_index), cfg.ctrl_exec, [this, ue_index, ul_logical_channels]() { + cfg.ue_exec_mapper.ctrl_executor(ue_index), cfg.ctrl_exec, [this, ue_index, ul_logical_channels]() { return ue_manager.addmod_bearers(ue_index, ul_logical_channels); }); } @@ -75,26 +76,28 @@ class mac_ul_processor final : public mac_ul_configurator, public mac_pdu_handle { std::vector lcids(lcids_to_rem.begin(), lcids_to_rem.end()); return dispatch_and_resume_on( - cfg.ue_exec_mapper.executor(ue_index), cfg.ctrl_exec, [this, ue_index, lcids = std::move(lcids)]() { + cfg.ue_exec_mapper.ctrl_executor(ue_index), cfg.ctrl_exec, [this, ue_index, lcids = std::move(lcids)]() { return ue_manager.remove_bearers(ue_index, lcids); }); } async_task remove_ue(const mac_ue_delete_request& msg) override { - return dispatch_and_resume_on(cfg.ue_exec_mapper.executor(msg.ue_index), + return dispatch_and_resume_on(cfg.ue_exec_mapper.ctrl_executor(msg.ue_index), cfg.ctrl_exec, [this, ue_index = msg.ue_index]() { ue_manager.remove_ue(ue_index); }); } - void flush_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer ccch_pdu) override + bool flush_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer ccch_pdu) override { - if (not cfg.ue_exec_mapper.executor(ue_index).execute([this, ue_index, pdu = std::move(ccch_pdu)]() mutable { + if (not cfg.ue_exec_mapper.ctrl_executor(ue_index).execute([this, ue_index, pdu = std::move(ccch_pdu)]() mutable { pdu_handler.push_ul_ccch_msg(ue_index, std::move(pdu)); })) { logger.warning("ue={}: Unable to forward UL-CCCH message to upper layers. Cause: task queue is full.", ue_index); - // Note: the inactivity timer will eventually destroy the UE. + // Note: The UE is not yet created in the CU, so there in no inactivity timer. + return false; } + return true; } /// Handles FAPI Rx_Data.Indication. @@ -108,7 +111,7 @@ class mac_ul_processor final : public mac_ul_configurator, public mac_pdu_handle du_ue_index_t ue_index = cfg.rnti_table[pdu.rnti]; // > Fork each PDU handling to different executors based on the PDU RNTI. - if (not cfg.ue_exec_mapper.executor(ue_index).execute( + if (not cfg.ue_exec_mapper.mac_ul_pdu_executor(ue_index).execute( [this, slot_rx = msg.sl_rx, cell_idx = msg.cell_index, pdu = std::move(pdu)]() mutable { // > Decode Rx PDU and handle respective subPDUs. pdu_handler.handle_rx_pdu(slot_rx, cell_idx, std::move(pdu)); diff --git a/lib/mac/mac_ul/mac_ul_ue_manager.cpp b/lib/mac/mac_ul/mac_ul_ue_manager.cpp index b44684e7ec..6116ffeabb 100644 --- a/lib/mac/mac_ul/mac_ul_ue_manager.cpp +++ b/lib/mac/mac_ul/mac_ul_ue_manager.cpp @@ -31,7 +31,8 @@ mac_ul_ue_manager::mac_ul_ue_manager(du_rnti_table& rnti_table_) : bool mac_ul_ue_manager::add_ue(const mac_ue_create_request& request) { - srsran_sanity_check(is_crnti(request.crnti), "Invalid C-RNTI={:#x}", request.crnti); + srsran_assert(is_crnti(request.crnti), "Invalid C-RNTI={:#x}", request.crnti); + srsran_assert(is_du_ue_index_valid(request.ue_index), "Invalid UE index={}", request.ue_index); // > Insert UE if (ue_db.contains(request.ue_index)) { diff --git a/lib/mac/mac_ul/pdu_rx_handler.cpp b/lib/mac/mac_ul/pdu_rx_handler.cpp index 946725c893..5cff75d3a0 100644 --- a/lib/mac/mac_ul/pdu_rx_handler.cpp +++ b/lib/mac/mac_ul/pdu_rx_handler.cpp @@ -103,13 +103,18 @@ bool pdu_rx_handler::handle_rx_pdu(slot_point sl_rx, du_cell_index_t cell_index, logger.info("{} subPDUs: [{}]", create_prefix(ctx), to_c_str(fmtbuf)); } - // > Check if MAC CRNTI CE is present. - for (unsigned n = ctx.decoded_subpdus.nof_subpdus(); n > 0; --n) { - const mac_ul_sch_subpdu& subpdu = ctx.decoded_subpdus.subpdu(n - 1); - - if (subpdu.lcid() == lcid_ul_sch_t::CRNTI) { - // >> Dispatch continuation of subPDU handling to execution context of previous C-RNTI. - return handle_crnti_ce(ctx, subpdu); + // > If Msg3 (UE index is still not assigned) is received, check if MAC CRNTI CE or UL-CCCH CE are present. + if (not is_du_ue_index_valid(ctx.ue_index)) { + for (unsigned n = ctx.decoded_subpdus.nof_subpdus(); n > 0; --n) { + const mac_ul_sch_subpdu& subpdu = ctx.decoded_subpdus.subpdu(n - 1); + + if (subpdu.lcid() == lcid_ul_sch_t::CRNTI) { + // >> Dispatch continuation of subPDU handling to execution context of previous C-RNTI. + return handle_crnti_ce(ctx, subpdu); + } + if (subpdu.lcid() == lcid_ul_sch_t::CCCH_SIZE_48 or subpdu.lcid() == lcid_ul_sch_t::CCCH_SIZE_64) { + return handle_ccch_msg(ctx, subpdu); + } } } @@ -186,8 +191,29 @@ bool pdu_rx_handler::handle_mac_ce(const decoded_mac_rx_pdu& ctx, const mac_ul_s // Handle MAC CEs switch (subpdu.lcid().value()) { case lcid_ul_sch_t::CCCH_SIZE_48: - case lcid_ul_sch_t::CCCH_SIZE_64: - return handle_ccch_msg(ctx, subpdu); + case lcid_ul_sch_t::CCCH_SIZE_64: { + // The CCCH CE is handled separately in case of Msg3, before all other CEs. See handle_rx_pdu(). + // At the point we enter this function, the CCCH should have been processed, and the UE should have been created. + // Here, we only ensure that we are not receiving CCCH for a non-Msg3 PUSCH. + if (is_du_ue_index_valid(ctx.ue_index)) { + // The UE already existed, so it should not be receiving an MAC UL-CCCH CE. However, there is a change that we + // received a PDU filled with zeros. In such case, we provide a clearer log message. + bool all_zeros = true; + for (span s : ctx.pdu_rx.pdu.segments()) { + if (std::any_of(s.begin(), s.end(), [](uint8_t v) { return v != 0; })) { + all_zeros = false; + break; + } + } + if (all_zeros) { + logger.warning("{}: Discarding PDU. Cause: Rx PDU is filled with zeros, meaning that it was likely corrupted", + create_prefix(ctx, subpdu)); + } else { + logger.warning("{}: Discarding PDU. Cause: UL-CCCH should be only for Msg3", create_prefix(ctx, subpdu)); + } + return false; + } + } break; case lcid_ul_sch_t::SHORT_BSR: case lcid_ul_sch_t::SHORT_TRUNC_BSR: case lcid_ul_sch_t::LONG_BSR: @@ -217,12 +243,23 @@ bool pdu_rx_handler::handle_mac_ce(const decoded_mac_rx_pdu& ctx, const mac_ul_s } sched.handle_ul_bsr_indication(bsr_ind); } break; - case lcid_ul_sch_t::CRNTI: - // The MAC CE C-RNTI is handled separately and, among all the MAC CEs, it should be the first one being processed. - // After the MAC C-RNTI is processed, this function is invoked for all subPDUs (including the MAC C-RNTI itself). - // Therefore, to avoid logging a warning for MAC C-RNTI, we added the case lcid_ul_sch_t::CRNTI below. - break; + case lcid_ul_sch_t::CRNTI: { + // The MAC C-RNTI CE is handled separately in case of Msg3, and before all other CEs. See handle_rx_pdu(). + // At the point we enter this function, the MAC C-RNTI CE should have already been processed, and the + // old UE context should have been retrieved. Here, we only ensure that the C-RNTI CE is not being received + // for a PUSCH that is not Msg3. + const bool crnti_ce_was_not_yet_processed = decode_crnti_ce(subpdu.payload()) != ctx.pdu_rx.rnti; + if (crnti_ce_was_not_yet_processed) { + logger.warning("{}: C-RNTI CE received in PUSCH that is not Msg3", create_prefix(ctx, subpdu)); + return false; + } + } break; case lcid_ul_sch_t::SE_PHR: { + if (not is_du_ue_index_valid(ctx.ue_index)) { + logger.warning("{}: Discarding MAC CE. Cause: C-RNTI is not associated with any existing UE", + create_prefix(ctx, subpdu)); + return false; + } mac_phr_ce_info phr_ind{}; phr_ind.cell_index = ctx.cell_index_rx; phr_ind.ue_index = ctx.ue_index; @@ -241,10 +278,8 @@ bool pdu_rx_handler::handle_mac_ce(const decoded_mac_rx_pdu& ctx, const mac_ul_s bool pdu_rx_handler::handle_ccch_msg(const decoded_mac_rx_pdu& ctx, const mac_ul_sch_subpdu& sdu) { - if (ctx.ue_index != INVALID_DU_UE_INDEX) { - logger.warning("{}: Discarding PDU. Cause: UL-CCCH should be only for Msg3", create_prefix(ctx, sdu)); - return true; - } + srsran_assert(ctx.ue_index == INVALID_DU_UE_INDEX, + "This function should only be called for Msg3, when UE context has not been created yet"); // Notify DU manager of received CCCH message. ul_ccch_indication_message msg{}; @@ -254,6 +289,11 @@ bool pdu_rx_handler::handle_ccch_msg(const decoded_mac_rx_pdu& ctx, const mac_ul msg.subpdu.append(sdu.payload()); ccch_notifier.on_ul_ccch_msg_received(msg); + // TODO: Do not discard remaining CEs. + if (ctx.decoded_subpdus.nof_subpdus() > 1) { + logger.debug("{}: Discarding remaining subPDUs", create_prefix(ctx, sdu)); + } + return true; } @@ -304,21 +344,31 @@ bool pdu_rx_handler::handle_crnti_ce(const decoded_mac_rx_pdu& ctx, const mac_ul } // > Dispatch continuation of subPDU handling to execution context of previous C-RNTI. - task_executor& ue_exec = ue_exec_mapper.executor(new_ctx.ue_index); + task_executor& ue_exec = ue_exec_mapper.mac_ul_pdu_executor(new_ctx.ue_index); if (not ue_exec.execute([this, new_ctx = std::move(new_ctx)]() { + if (ue_manager.find_ue(new_ctx.ue_index) == nullptr) { + logger.warning( + "{}: Discarding PDU. Cause: UE with C-RNTI in C-RNTI CE has been deleted while the CE was being handled", + create_prefix(new_ctx)); + return; + } + // >> Handle remaining subPDUs using old C-RNTI. if (not handle_rx_subpdus(new_ctx)) { return; } + // >> Notify scheduler of received C-RNTI CE. + sched.handle_crnti_ce_indication(new_ctx.ue_index); + // >> In case no positive BSR was provided, we force a positive BSR in the scheduler to complete the RA - // procedure. + // procedure, as per TS 38.321, Section 5.1.5. if (not contains_positive_bsr(new_ctx.decoded_subpdus)) { sched.handle_ul_sched_command( mac_ul_scheduling_command{new_ctx.cell_index_rx, new_ctx.slot_rx, new_ctx.ue_index, new_ctx.pdu_rx.rnti}); } })) { - logger.warning("{}: Discarding PDU. Cause: Task queue is full.", create_prefix(new_ctx, subpdu)); + logger.warning("{}: Discarding PDU. Cause: Task queue is full.", create_prefix(ctx, subpdu)); } return true; diff --git a/lib/ngap/CMakeLists.txt b/lib/ngap/CMakeLists.txt index 3ad5bc2a42..2b707ea86e 100644 --- a/lib/ngap/CMakeLists.txt +++ b/lib/ngap/CMakeLists.txt @@ -24,6 +24,7 @@ set(SOURCES ngap_asn1_packer.cpp procedures/ng_setup_procedure.cpp procedures/ngap_initial_context_setup_procedure.cpp + procedures/ngap_dl_nas_message_transfer_procedure.cpp procedures/ngap_pdu_session_resource_setup_procedure.cpp procedures/ngap_pdu_session_resource_modify_procedure.cpp procedures/ngap_pdu_session_resource_release_procedure.cpp diff --git a/lib/ngap/ngap_asn1_helpers.h b/lib/ngap/ngap_asn1_helpers.h index 8b83de8a99..b3aba78b4e 100644 --- a/lib/ngap/ngap_asn1_helpers.h +++ b/lib/ngap/ngap_asn1_helpers.h @@ -23,11 +23,14 @@ #pragma once #include "ngap_asn1_converters.h" +#include "ngap_context.h" +#include "procedures/ngap_initial_context_setup_procedure.h" #include "srsran/adt/byte_buffer.h" #include "srsran/adt/optional.h" #include "srsran/asn1/asn1_utils.h" #include "srsran/asn1/ngap/ngap_ies.h" #include "srsran/asn1/ngap/ngap_pdu_contents.h" +#include "srsran/cu_cp/cu_cp_types.h" #include "srsran/ngap/ngap.h" #include "srsran/ngap/ngap_configuration.h" #include "srsran/ngap/ngap_handover.h" @@ -85,72 +88,43 @@ inline void fill_asn1_ng_setup_request(asn1::ngap::ng_setup_request_s& request, request->default_paging_drx.value = asn1::ngap::paging_drx_opts::v256; } -/// \brief Convert common type Initial Context Setup Response message to NGAP Initial Context Setup Response -/// message. -/// \param[out] asn1_resp The ASN1 NGAP Initial Context Setup Response message. -/// \param[in] resp The CU-CP Initial Context Setup Response message. -inline void fill_asn1_initial_context_setup_response(asn1::ngap::init_context_setup_resp_s& asn1_resp, - const ngap_initial_context_response_message& resp) +/// \brief Convert common type Initial UE Message to NGAP Initial UE Message. +/// \param[out] asn1_msg The ASN1 NGAP Initial UE Message. +/// \param[in] msg The CU-CP Initial UE Message. +inline void fill_asn1_initial_ue_message(asn1::ngap::init_ue_msg_s& asn1_msg, + const cu_cp_initial_ue_message& msg, + const ngap_context_t& context) { - // Fill PDU Session Resource Setup Response List - if (!resp.pdu_session_res_setup_response_items.empty()) { - asn1_resp->pdu_session_res_setup_list_cxt_res_present = true; + asn1_msg->nas_pdu = msg.nas_pdu.copy(); - for (const auto& resp_item : resp.pdu_session_res_setup_response_items) { - asn1::ngap::pdu_session_res_setup_item_cxt_res_s asn1_resp_item; - - pdu_session_res_setup_response_item_to_asn1(asn1_resp_item, resp_item); - - asn1_resp->pdu_session_res_setup_list_cxt_res.push_back(asn1_resp_item); - } - } + asn1_msg->rrc_establishment_cause.value = + static_cast(msg.establishment_cause); - // Fill PDU Session Resource Failed to Setup List - if (!resp.pdu_session_res_failed_to_setup_items.empty()) { - asn1_resp->pdu_session_res_failed_to_setup_list_cxt_res_present = true; - for (const auto& setup_failed_item : resp.pdu_session_res_failed_to_setup_items) { - asn1::ngap::pdu_session_res_failed_to_setup_item_cxt_res_s asn1_setup_failed_item; + auto& user_loc_info_nr = asn1_msg->user_location_info.set_user_location_info_nr(); + user_loc_info_nr = cu_cp_user_location_info_to_asn1(msg.user_location_info); - pdu_session_res_setup_failed_item_to_asn1(asn1_setup_failed_item, setup_failed_item); + asn1_msg->ue_context_request_present = true; + asn1_msg->ue_context_request.value = asn1::ngap::ue_context_request_opts::options::requested; - asn1_resp->pdu_session_res_failed_to_setup_list_cxt_res.push_back(asn1_setup_failed_item); - } + if (msg.five_g_s_tmsi.has_value()) { + asn1_msg->five_g_s_tmsi_present = true; + asn1_msg->five_g_s_tmsi.amf_set_id.from_number(context.current_guami.amf_set_id); + asn1_msg->five_g_s_tmsi.amf_pointer.from_number(context.current_guami.amf_pointer); + asn1_msg->five_g_s_tmsi.five_g_tmsi.from_number(msg.five_g_s_tmsi.value().five_g_tmsi); } - // Fill Criticality Diagnostics - if (resp.crit_diagnostics.has_value()) { - asn1_resp->crit_diagnostics_present = true; - asn1_resp->crit_diagnostics = resp.crit_diagnostics.value(); - } + // TODO: Add missing optional values } -/// \brief Convert common type Initial Context Setup Failure message to NGAP Initial Context Setup Failure -/// message. -/// \param[out] asn1_fail The ASN1 NGAP Initial Context Setup Failure message. -/// \param[in] fail The CU-CP Initial Context Setup Failure message. -inline void fill_asn1_initial_context_setup_failure(asn1::ngap::init_context_setup_fail_s& asn1_fail, - const ngap_initial_context_failure_message& fail) +/// \brief Convert common type UL NAS Transport message to NGAP UL NAS Transport message. +/// \param[out] asn1_msg The ASN1 NGAP UL NAS Transport message. +/// \param[in] msg The CU-CP UL NAS Transport message. +inline void fill_asn1_ul_nas_transport(asn1::ngap::ul_nas_transport_s& asn1_msg, const cu_cp_ul_nas_transport& msg) { - // Fill cause - asn1_fail->cause = fail.cause; - - // Fill PDU Session Resource Failed to Setup List - if (!fail.pdu_session_res_failed_to_setup_items.empty()) { - asn1_fail->pdu_session_res_failed_to_setup_list_cxt_fail_present = true; - for (const auto& setup_failed_item : fail.pdu_session_res_failed_to_setup_items) { - asn1::ngap::pdu_session_res_failed_to_setup_item_cxt_fail_s asn1_setup_failed_item; - - pdu_session_res_setup_failed_item_to_asn1(asn1_setup_failed_item, setup_failed_item); + asn1_msg->nas_pdu = msg.nas_pdu.copy(); - asn1_fail->pdu_session_res_failed_to_setup_list_cxt_fail.push_back(asn1_setup_failed_item); - } - } - - // Fill Criticality Diagnostics - if (fail.crit_diagnostics.has_value()) { - asn1_fail->crit_diagnostics_present = true; - asn1_fail->crit_diagnostics = fail.crit_diagnostics.value(); - } + auto& user_loc_info_nr = asn1_msg->user_location_info.set_user_location_info_nr(); + user_loc_info_nr = cu_cp_user_location_info_to_asn1(msg.user_location_info); } /// Helper function to fill the CU-CP PDU Session Resource Setup Item for both, PDUSessionResourceSetupItemSUReq and @@ -299,6 +273,181 @@ inline bool fill_cu_cp_pdu_session_resource_setup_request( return true; } +/// \brief Convert NGAP ASN1 PDU Session Resource Setup List CTX REQ +/// ASN1 struct to common type. +/// \param[out] cu_cp_pdu_session_res_setup_msg The cu_cp_pdu_session_res_setup_msg struct to fill. +/// \param[in] asn1_pdu_session_res_setup_list The pdu_session_res_setup_item_cxt_req_s ASN1 struct. +/// \returns True if the conversion was successful, false otherwise. +inline bool fill_cu_cp_pdu_session_resource_setup_request( + cu_cp_pdu_session_resource_setup_request& cu_cp_pdu_session_resource_setup_msg, + const asn1::dyn_seq_of& + asn1_pdu_session_res_setup_list) +{ + for (const auto& asn1_session_item : asn1_pdu_session_res_setup_list) { + cu_cp_pdu_session_res_setup_item setup_item; + + if (!fill_cu_cp_pdu_session_resource_setup_item_base( + setup_item, asn1_session_item, asn1_session_item.pdu_session_res_setup_request_transfer.copy())) { + return false; + } + + // NAS-PDU + if (!asn1_session_item.nas_pdu.empty()) { + setup_item.pdu_session_nas_pdu.resize(asn1_session_item.nas_pdu.size()); + std::copy( + asn1_session_item.nas_pdu.begin(), asn1_session_item.nas_pdu.end(), setup_item.pdu_session_nas_pdu.begin()); + } + + cu_cp_pdu_session_resource_setup_msg.pdu_session_res_setup_items.emplace(setup_item.pdu_session_id, + std::move(setup_item)); + } + return true; +} + +/// \brief Convert NGAP ASN1 Init Context Setup Request ASN1 struct to common type. +/// \param[out] request The ngap_init_context_setup_request struct to fill. +/// \param[in] asn1_request The Init Context Setup Request ASN1 struct. +inline bool fill_ngap_initial_context_setup_request(ngap_init_context_setup_request& request, + const asn1::ngap::init_context_setup_request_s& asn1_request) +{ + // old_amf + if (asn1_request->old_amf_present) { + request.old_amf = asn1_request->old_amf.to_string(); + } + + // ue_aggr_max_bit_rate + if (asn1_request->ue_aggr_max_bit_rate_present) { + request.ue_aggr_max_bit_rate.emplace(); + request.ue_aggr_max_bit_rate.value().ue_aggr_max_bit_rate_dl = + asn1_request->ue_aggr_max_bit_rate.ue_aggr_max_bit_rate_dl; + request.ue_aggr_max_bit_rate.value().ue_aggr_max_bit_rate_ul = + asn1_request->ue_aggr_max_bit_rate.ue_aggr_max_bit_rate_ul; + } + + // guami + request.guami = asn1_to_guami(asn1_request->guami); + + // pdu_session_res_setup_list_cxt_req + if (asn1_request->pdu_session_res_setup_list_cxt_req_present) { + request.pdu_session_res_setup_list_cxt_req.emplace(); + if (!fill_cu_cp_pdu_session_resource_setup_request(request.pdu_session_res_setup_list_cxt_req.value(), + asn1_request->pdu_session_res_setup_list_cxt_req)) { + return false; + } + } + + // allowed_nssai + for (const auto& asn1_s_nssai : asn1_request->allowed_nssai) { + request.allowed_nssai.push_back(ngap_asn1_to_s_nssai(asn1_s_nssai.s_nssai)); + } + + // security_context + copy_asn1_key(request.security_context.k, asn1_request->security_key); + fill_supported_algorithms(request.security_context.supported_int_algos, + asn1_request->ue_security_cap.nr_integrity_protection_algorithms); + fill_supported_algorithms(request.security_context.supported_enc_algos, + asn1_request->ue_security_cap.nr_encryption_algorithms); + + // ue_radio_cap + if (asn1_request->ue_radio_cap_present) { + request.ue_radio_cap = asn1_request->ue_radio_cap.copy(); + } + + // idx_to_rfsp + if (asn1_request->idx_to_rfsp_present) { + request.idx_to_rfsp = asn1_request->idx_to_rfsp; + } + + // masked_imeisv + if (asn1_request->masked_imeisv_present) { + request.masked_imeisv = asn1_request->masked_imeisv.to_number(); + } + + // nas_pdu + if (asn1_request->nas_pdu_present) { + request.nas_pdu = asn1_request->nas_pdu.copy(); + } + + // ue_radio_cap_for_paging + if (asn1_request->ue_radio_cap_for_paging_present) { + cu_cp_ue_radio_cap_for_paging ue_radio_cap_for_paging; + ue_radio_cap_for_paging.ue_radio_cap_for_paging_of_nr = + asn1_request->ue_radio_cap_for_paging.ue_radio_cap_for_paging_of_nr.copy(); + + request.ue_radio_cap_for_paging = ue_radio_cap_for_paging; + } + + // TODO: Add missing optional values + + return true; +} + +/// \brief Convert common type Initial Context Setup Response message to NGAP Initial Context Setup Response +/// message. +/// \param[out] asn1_resp The ASN1 NGAP Initial Context Setup Response message. +/// \param[in] resp The CU-CP Initial Context Setup Response message. +inline void fill_asn1_initial_context_setup_response(asn1::ngap::init_context_setup_resp_s& asn1_resp, + const ngap_init_context_setup_response& resp) +{ + // Fill PDU Session Resource Setup Response List + if (!resp.pdu_session_res_setup_response_items.empty()) { + asn1_resp->pdu_session_res_setup_list_cxt_res_present = true; + + for (const auto& resp_item : resp.pdu_session_res_setup_response_items) { + asn1::ngap::pdu_session_res_setup_item_cxt_res_s asn1_resp_item; + + pdu_session_res_setup_response_item_to_asn1(asn1_resp_item, resp_item); + + asn1_resp->pdu_session_res_setup_list_cxt_res.push_back(asn1_resp_item); + } + } + + // Fill PDU Session Resource Failed to Setup List + if (!resp.pdu_session_res_failed_to_setup_items.empty()) { + asn1_resp->pdu_session_res_failed_to_setup_list_cxt_res_present = true; + for (const auto& setup_failed_item : resp.pdu_session_res_failed_to_setup_items) { + asn1::ngap::pdu_session_res_failed_to_setup_item_cxt_res_s asn1_setup_failed_item; + + pdu_session_res_setup_failed_item_to_asn1(asn1_setup_failed_item, setup_failed_item); + + asn1_resp->pdu_session_res_failed_to_setup_list_cxt_res.push_back(asn1_setup_failed_item); + } + } + + // Fill Criticality Diagnostics + if (resp.crit_diagnostics.has_value()) { + // TODO: Add crit diagnostics + } +} + +/// \brief Convert common type Initial Context Setup Failure message to NGAP Initial Context Setup Failure +/// message. +/// \param[out] asn1_fail The ASN1 NGAP Initial Context Setup Failure message. +/// \param[in] fail The CU-CP Initial Context Setup Failure message. +inline void fill_asn1_initial_context_setup_failure(asn1::ngap::init_context_setup_fail_s& asn1_fail, + const ngap_init_context_setup_failure& fail) +{ + // Fill cause + asn1_fail->cause = cause_to_asn1(fail.cause); + + // Fill PDU Session Resource Failed to Setup List + if (!fail.pdu_session_res_failed_to_setup_items.empty()) { + asn1_fail->pdu_session_res_failed_to_setup_list_cxt_fail_present = true; + for (const auto& setup_failed_item : fail.pdu_session_res_failed_to_setup_items) { + asn1::ngap::pdu_session_res_failed_to_setup_item_cxt_fail_s asn1_setup_failed_item; + + pdu_session_res_setup_failed_item_to_asn1(asn1_setup_failed_item, setup_failed_item); + + asn1_fail->pdu_session_res_failed_to_setup_list_cxt_fail.push_back(asn1_setup_failed_item); + } + } + + // Fill Criticality Diagnostics + if (fail.crit_diagnostics.has_value()) { + // TODO: Add crit diagnostics + } +} + /// \brief Convert a NGAP ASN1 modify item to commong type. /// \param[out] modify_item The flat/common version /// \param[in] asn1_session_item The ASN1 struct to be converted. @@ -390,37 +539,6 @@ inline void fill_cu_cp_pdu_session_resource_modify_request( } } -/// \brief Convert NGAP ASN1 PDU Session Resource Setup List CTX REQ -/// ASN1 struct to common type. -/// \param[out] cu_cp_pdu_session_res_setup_msg The cu_cp_pdu_session_res_setup_msg struct to fill. -/// \param[in] asn1_pdu_session_res_setup_list The pdu_session_res_setup_item_cxt_req_s ASN1 struct. -/// \returns True if the conversion was successful, false otherwise. -inline bool fill_cu_cp_pdu_session_resource_setup_request( - cu_cp_pdu_session_resource_setup_request& cu_cp_pdu_session_resource_setup_msg, - const asn1::dyn_seq_of& - asn1_pdu_session_res_setup_list) -{ - for (const auto& asn1_session_item : asn1_pdu_session_res_setup_list) { - cu_cp_pdu_session_res_setup_item setup_item; - - if (!fill_cu_cp_pdu_session_resource_setup_item_base( - setup_item, asn1_session_item, asn1_session_item.pdu_session_res_setup_request_transfer.copy())) { - return false; - } - - // NAS-PDU - if (!asn1_session_item.nas_pdu.empty()) { - setup_item.pdu_session_nas_pdu.resize(asn1_session_item.nas_pdu.size()); - std::copy( - asn1_session_item.nas_pdu.begin(), asn1_session_item.nas_pdu.end(), setup_item.pdu_session_nas_pdu.begin()); - } - - cu_cp_pdu_session_resource_setup_msg.pdu_session_res_setup_items.emplace(setup_item.pdu_session_id, - std::move(setup_item)); - } - return true; -} - /// \brief Convert common type PDU Session Resource Setup Response message to NGAP PDU Session Resource Setup Response /// message. /// \param[out] resp The ASN1 NGAP PDU Session Resource Setup Response message. diff --git a/lib/ngap/ngap_asn1_utils.h b/lib/ngap/ngap_asn1_utils.h index 7c47ef02c6..a567e69036 100644 --- a/lib/ngap/ngap_asn1_utils.h +++ b/lib/ngap/ngap_asn1_utils.h @@ -24,7 +24,7 @@ #include "srsran/adt/expected.h" #include "srsran/asn1/ngap/ngap.h" -#include "srsran/ngap/ngap_types.h" +#include "srsran/cu_cp/cu_cp_types.h" #include "srsran/security/security.h" #include "srsran/support/error_handling.h" diff --git a/lib/ngap/ngap_context.h b/lib/ngap/ngap_context.h index fe9144c869..66ebf19775 100644 --- a/lib/ngap/ngap_context.h +++ b/lib/ngap/ngap_context.h @@ -22,7 +22,7 @@ #pragma once -#include "srsran/cu_cp/cu_cp_types.h" +#include "srsran/ngap/ngap_types.h" #include namespace srsran { @@ -37,6 +37,7 @@ struct ngap_context_t { unsigned tac; std::vector served_guami_list; guami_t current_guami; + std::chrono::seconds ue_context_setup_timeout_s; // timeout for ue context setup in seconds }; } // namespace srs_cu_cp diff --git a/lib/ngap/ngap_impl.cpp b/lib/ngap/ngap_impl.cpp index 836b5fdcf8..3ceda62184 100644 --- a/lib/ngap/ngap_impl.cpp +++ b/lib/ngap/ngap_impl.cpp @@ -24,6 +24,7 @@ #include "ngap_asn1_helpers.h" #include "ngap_asn1_utils.h" #include "procedures/ng_setup_procedure.h" +#include "procedures/ngap_dl_nas_message_transfer_procedure.h" #include "procedures/ngap_handover_preparation_procedure.h" #include "procedures/ngap_handover_resource_allocation_procedure.h" #include "procedures/ngap_initial_context_setup_procedure.h" @@ -31,6 +32,9 @@ #include "procedures/ngap_pdu_session_resource_release_procedure.h" #include "procedures/ngap_pdu_session_resource_setup_procedure.h" #include "procedures/ngap_ue_context_release_procedure.h" +#include "srsran/ngap/ngap_types.h" +#include "srsran/ran/cause.h" +#include "srsran/support/srsran_assert.h" using namespace srsran; using namespace asn1::ngap; @@ -43,6 +47,7 @@ ngap_impl::ngap_impl(ngap_configuration& ngap_cfg_, ngap_message_notifier& ngap_notifier_, task_executor& ctrl_exec_) : logger(srslog::fetch_basic_logger("NGAP")), + ue_ctxt_list(logger), cu_cp_du_repository_notifier(cu_cp_du_repository_notifier_), task_sched(task_sched_), ue_manager(ue_manager_), @@ -50,10 +55,11 @@ ngap_impl::ngap_impl(ngap_configuration& ngap_cfg_, ctrl_exec(ctrl_exec_), ev_mng(timer_factory{task_sched.get_timer_manager(), ctrl_exec}) { - context.gnb_id = ngap_cfg_.gnb_id; - context.ran_node_name = ngap_cfg_.ran_node_name; - context.plmn = ngap_cfg_.plmn; - context.tac = ngap_cfg_.tac; + context.gnb_id = ngap_cfg_.gnb_id; + context.ran_node_name = ngap_cfg_.ran_node_name; + context.plmn = ngap_cfg_.plmn; + context.tac = ngap_cfg_.tac; + context.ue_context_setup_timeout_s = ngap_cfg_.ue_context_setup_timeout_s; } // Note: For fwd declaration of member types, dtor cannot be trivial. @@ -64,7 +70,16 @@ void ngap_impl::create_ngap_ue(ue_index_t ue_index, ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier, ngap_du_processor_control_notifier& du_processor_ctrl_notifier) { + ran_ue_id_t ran_ue_id = ue_ctxt_list.get_next_ran_ue_id(); + if (ran_ue_id == ran_ue_id_t::invalid) { + logger.error("ue={}: No RAN-UE-ID available", ue_index); + return; + } + // Create UE context and store it + ue_ctxt_list.add_ue(ue_index, ran_ue_id, task_sched.get_timer_manager(), ctrl_exec); + + // Add NGAP UE to UE manager ngap_ue* ue = ue_manager.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_ctrl_notifier, du_processor_ctrl_notifier); if (ue == nullptr) { @@ -72,7 +87,23 @@ void ngap_impl::create_ngap_ue(ue_index_t ue_index, return; } - logger.debug("ue={} ran_ue_id={}: Created UE", ue_index, ue->get_ran_ue_id()); + logger.debug("ue={} ran_ue_id={}: Created UE", ue_index, ran_ue_id); +} + +bool ngap_impl::update_ue_index(ue_index_t new_ue_index, ue_index_t old_ue_index) +{ + if (!ue_ctxt_list.contains(old_ue_index)) { + logger.warning("Failed to transfer NGAP UE context from ue={} to ue={}. Old UE context does not exist", + old_ue_index, + new_ue_index); + return false; + } + + logger.debug("Transferring NGAP UE context from ue={} to ue={}", old_ue_index, new_ue_index); + + ue_ctxt_list.update_ue_index(new_ue_index, old_ue_index); + + return true; } async_task ngap_impl::handle_ng_setup_request(const ng_setup_request& request) @@ -82,84 +113,69 @@ async_task ngap_impl::handle_ng_setup_request(const ng_setup_ context, request, ngap_notifier, ev_mng, timer_factory{task_sched.get_timer_manager(), ctrl_exec}, logger); } -void ngap_impl::handle_initial_ue_message(const ngap_initial_ue_message& msg) +void ngap_impl::handle_initial_ue_message(const cu_cp_initial_ue_message& msg) { - auto* ue = ue_manager.find_ngap_ue(msg.ue_index); - if (ue == nullptr) { - logger.warning("ue={}: Dropping InitialUeMessage. UE does not exist", msg.ue_index); + if (!ue_ctxt_list.contains(msg.ue_index)) { + logger.warning("ue={}: Dropping InitialUeMessage. UE context does not exist", msg.ue_index); return; } + ngap_ue_context& ue_ctxt = ue_ctxt_list[msg.ue_index]; + ngap_message ngap_msg = {}; ngap_msg.pdu.set_init_msg(); ngap_msg.pdu.init_msg().load_info_obj(ASN1_NGAP_ID_INIT_UE_MSG); auto& init_ue_msg = ngap_msg.pdu.init_msg().value.init_ue_msg(); - init_ue_msg->ran_ue_ngap_id = ran_ue_id_to_uint(ue->get_ran_ue_id()); - - init_ue_msg->nas_pdu.resize(msg.nas_pdu.length()); - std::copy(msg.nas_pdu.begin(), msg.nas_pdu.end(), init_ue_msg->nas_pdu.begin()); + init_ue_msg->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ctxt.ue_ids.ran_ue_id); - init_ue_msg->rrc_establishment_cause.value = msg.establishment_cause.value; - - auto& user_loc_info_nr = init_ue_msg->user_location_info.set_user_location_info_nr(); - user_loc_info_nr.nr_cgi = msg.nr_cgi; - user_loc_info_nr.tai.plmn_id = msg.nr_cgi.plmn_id; - user_loc_info_nr.tai.tac.from_number(msg.tac); - - init_ue_msg->ue_context_request_present = true; - init_ue_msg->ue_context_request.value = asn1::ngap::ue_context_request_opts::options::requested; - - if (msg.five_g_s_tmsi.has_value()) { - init_ue_msg->five_g_s_tmsi_present = true; - init_ue_msg->five_g_s_tmsi.amf_set_id.from_number(context.current_guami.amf_set_id); - init_ue_msg->five_g_s_tmsi.amf_pointer.from_number(context.current_guami.amf_pointer); - init_ue_msg->five_g_s_tmsi.five_g_tmsi.from_number(msg.five_g_s_tmsi.value().five_g_tmsi); - } + fill_asn1_initial_ue_message(init_ue_msg, msg, context); - // TODO: Add missing optional values + // Start UE context setup timer + ue_ctxt.ue_context_setup_timer.set(context.ue_context_setup_timeout_s, [this, msg](timer_id_t /*tid*/) { + on_ue_context_setup_timer_expired(msg.ue_index); + }); + ue_ctxt.ue_context_setup_timer.run(); - logger.info("ue={} ran_ue_id={}: Sending InitialUeMessage", msg.ue_index, ue->get_ran_ue_id()); + logger.info("ue={} ran_ue_id={}: Sending InitialUeMessage (timeout={}s)", + msg.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_context_setup_timer.duration().count()); // Forward message to AMF ngap_notifier.on_new_message(ngap_msg); } -void ngap_impl::handle_ul_nas_transport_message(const ngap_ul_nas_transport_message& msg) +void ngap_impl::handle_ul_nas_transport_message(const cu_cp_ul_nas_transport& msg) { - auto* ue = ue_manager.find_ngap_ue(msg.ue_index); - if (ue == nullptr) { - logger.warning("ue={}: Dropping UlNasTransportMessage. UE does not exist", msg.ue_index); + if (!ue_ctxt_list.contains(msg.ue_index)) { + logger.warning("ue={}: Dropping UlNasTransportMessage. UE context does not exist", msg.ue_index); return; } + ngap_ue_context& ue_ctxt = ue_ctxt_list[msg.ue_index]; + ngap_message ngap_msg = {}; ngap_msg.pdu.set_init_msg(); ngap_msg.pdu.init_msg().load_info_obj(ASN1_NGAP_ID_UL_NAS_TRANSPORT); auto& ul_nas_transport_msg = ngap_msg.pdu.init_msg().value.ul_nas_transport(); - ul_nas_transport_msg->ran_ue_ngap_id = ran_ue_id_to_uint(ue->get_ran_ue_id()); + ul_nas_transport_msg->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ctxt.ue_ids.ran_ue_id); - amf_ue_id_t amf_ue_id = ue->get_amf_ue_id(); + amf_ue_id_t amf_ue_id = ue_ctxt.ue_ids.amf_ue_id; if (amf_ue_id == amf_ue_id_t::invalid) { - logger.warning("ue={}: UE AMF ID not found", msg.ue_index); + logger.warning("ue={}: Dropping UL NAS transport. UE AMF ID not found", msg.ue_index); return; } ul_nas_transport_msg->amf_ue_ngap_id = amf_ue_id_to_uint(amf_ue_id); - ul_nas_transport_msg->nas_pdu.resize(msg.nas_pdu.length()); - std::copy(msg.nas_pdu.begin(), msg.nas_pdu.end(), ul_nas_transport_msg->nas_pdu.begin()); - - auto& user_loc_info_nr = ul_nas_transport_msg->user_location_info.set_user_location_info_nr(); - user_loc_info_nr.nr_cgi = msg.nr_cgi; - user_loc_info_nr.tai.plmn_id = msg.nr_cgi.plmn_id; - user_loc_info_nr.tai.tac.from_number(msg.tac); + fill_asn1_ul_nas_transport(ul_nas_transport_msg, msg); logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending UlNasTransportMessage", msg.ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); // Forward message to AMF ngap_notifier.on_new_message(ngap_msg); @@ -167,7 +183,7 @@ void ngap_impl::handle_ul_nas_transport_message(const ngap_ul_nas_transport_mess void ngap_impl::handle_message(const ngap_message& msg) { - logger.debug("Handling PDU of type \"{}.{}\"", msg.pdu.type().to_string(), get_message_type_str(msg.pdu)); + logger.debug("Received PDU of type \"{}.{}\"", msg.pdu.type().to_string(), get_message_type_str(msg.pdu)); if (logger.debug.enabled()) { asn1::json_writer js; @@ -231,140 +247,228 @@ void ngap_impl::handle_initiating_message(const init_msg_s& msg) void ngap_impl::handle_dl_nas_transport_message(const asn1::ngap::dl_nas_transport_s& msg) { - ue_index_t ue_index = ue_manager.get_ue_index(uint_to_ran_ue_id(msg->ran_ue_ngap_id)); - if (ue_index == ue_index_t::invalid) { - logger.warning("ue={} ran_ue_id={} amf_ue_id={}: Dropping DlNasTransportMessage. UE does not exist", - ue_index, + if (!ue_ctxt_list.contains(uint_to_ran_ue_id(msg->ran_ue_ngap_id))) { + logger.warning("ran_ue_id={} amf_ue_id={}: Dropping DlNasTransportMessage. UE context does not exist", msg->ran_ue_ngap_id, msg->amf_ue_ngap_id); - send_error_indication(ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + send_error_indication(ue_index_t::invalid, cause_radio_network_t::unknown_local_ue_ngap_id); return; } - auto* ue = ue_manager.find_ngap_ue(ue_index); - if (ue == nullptr) { - logger.warning("ue={} ran_ue_id={} amf_ue_id={}: Dropping PDU. UE does not exist", - ue_index, - msg->ran_ue_ngap_id, - msg->amf_ue_ngap_id); - send_error_indication(ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + ngap_ue_context& ue_ctxt = ue_ctxt_list[uint_to_ran_ue_id(msg->ran_ue_ngap_id)]; + + if (ue_ctxt.release_scheduled) { + logger.info("ran_ue_id={} amf_ue_id={}: Dropping DlNasTransportMessage. UE is already scheduled for release", + msg->ran_ue_ngap_id, + msg->amf_ue_ngap_id); + schedule_error_indication(ue_ctxt.ue_ids.ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); return; } + auto* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); + srsran_assert(ue != nullptr, + "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + logger.info("ue={} ran_ue_id={} amf_ue_id={}: Received DlNasTransportMessage", - ue_index, - ue->get_ran_ue_id(), - uint_to_amf_ue_id(msg->amf_ue_ngap_id)); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + msg->amf_ue_ngap_id); // Add AMF UE ID to ue ngap context if it is not set (this is the first DL NAS Transport message) - if (ue->get_amf_ue_id() == amf_ue_id_t::invalid) { + if (ue_ctxt.ue_ids.amf_ue_id == amf_ue_id_t::invalid) { // Set AMF UE ID in the UE context and also in the lookup - ue_manager.set_amf_ue_id(ue_index, uint_to_amf_ue_id(msg->amf_ue_ngap_id)); + ue_ctxt_list.add_amf_ue_id(ue_ctxt.ue_ids.ran_ue_id, uint_to_amf_ue_id(msg->amf_ue_ngap_id)); } - byte_buffer nas_pdu; - nas_pdu.resize(msg->nas_pdu.size()); - std::copy(msg->nas_pdu.begin(), msg->nas_pdu.end(), nas_pdu.begin()); - logger.debug(nas_pdu.begin(), nas_pdu.end(), "DlNasTransport PDU ({} B)", nas_pdu.length()); - - ue->get_rrc_ue_pdu_notifier().on_new_pdu(std::move(nas_pdu)); + // start routine + task_sched.schedule_async_task(ue_ctxt.ue_ids.ue_index, + launch_async( + msg->nas_pdu.copy(), ue_ctxt.ue_ids, ue->get_rrc_ue_pdu_notifier(), logger)); } void ngap_impl::handle_initial_context_setup_request(const asn1::ngap::init_context_setup_request_s& request) { - ue_index_t ue_index = ue_manager.get_ue_index(uint_to_ran_ue_id(request->ran_ue_ngap_id)); - auto* ue = ue_manager.find_ngap_ue(ue_index); - if (ue == nullptr) { - logger.warning("ue={}: Dropping InitialContextSetupRequest. UE does not exist", ue_index); - send_error_indication(ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + if (!ue_ctxt_list.contains(uint_to_ran_ue_id(request->ran_ue_ngap_id))) { + logger.warning("ran_ue_id={} amf_ue_id={}: Dropping InitialContextSetupRequest. UE context does not exist", + request->ran_ue_ngap_id, + request->amf_ue_ngap_id); + send_error_indication(ue_index_t::invalid, cause_radio_network_t::unknown_local_ue_ngap_id); return; } + ngap_ue_context& ue_ctxt = ue_ctxt_list[uint_to_ran_ue_id(request->ran_ue_ngap_id)]; + + if (ue_ctxt.release_scheduled) { + logger.info("ran_ue_id={} amf_ue_id={}: Dropping InitialContextSetup. UE is already scheduled for release", + request->ran_ue_ngap_id, + request->amf_ue_ngap_id); + schedule_error_indication(ue_ctxt.ue_ids.ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + return; + } + + auto* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); + srsran_assert(ue != nullptr, + "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + + // Stop UE context setup timer + ue_ctxt.ue_context_setup_timer.stop(); + logger.info("ue={} ran_ue_id={} amf_ue_id={}: Received InitialContextSetupRequest", - ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + request->amf_ue_ngap_id); // Update AMF ID and use the one from this Context Setup as per TS 38.413 v16.2 page 38 - ue_manager.set_amf_ue_id(ue_index, uint_to_amf_ue_id(request->amf_ue_ngap_id)); + ue_ctxt_list.add_amf_ue_id(ue_ctxt.ue_ids.ran_ue_id, uint_to_amf_ue_id(request->amf_ue_ngap_id)); + + // Convert to common type + ngap_init_context_setup_request init_ctxt_setup_req; + init_ctxt_setup_req.ue_index = ue_ctxt.ue_ids.ue_index; + if (!fill_ngap_initial_context_setup_request(init_ctxt_setup_req, request)) { + logger.error("ue={} ran_ue_id={} amf_ue_id={}: Conversion of PDU Session Resource Setup Request failed.", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + send_error_indication(ue_ctxt.ue_ids.ue_index, {}, ue_ctxt.ue_ids.amf_ue_id); + return; + } + + // Store guami + context.current_guami = init_ctxt_setup_req.guami; + + // Store UE Aggregate Maximum Bitrate + if (init_ctxt_setup_req.ue_aggr_max_bit_rate.has_value()) { + ue_ctxt.aggregate_maximum_bit_rate_dl = init_ctxt_setup_req.ue_aggr_max_bit_rate.value().ue_aggr_max_bit_rate_dl; + } + + // Log security context + logger.debug(request->security_key.data(), 32, "K_gnb"); + logger.debug("Supported integrity algorithms: {}", init_ctxt_setup_req.security_context.supported_int_algos); + logger.debug("Supported ciphering algorithms: {}", init_ctxt_setup_req.security_context.supported_enc_algos); // start routine - task_sched.schedule_async_task(ue_index, - launch_async( - context, ue_index, request, ue_manager, ngap_notifier, logger)); + task_sched.schedule_async_task( + ue_ctxt.ue_ids.ue_index, + launch_async(init_ctxt_setup_req, + ue_ctxt, + ue->get_rrc_ue_control_notifier(), + ue->get_rrc_ue_pdu_notifier(), + ue->get_du_processor_control_notifier(), + ngap_notifier, + logger)); } void ngap_impl::handle_pdu_session_resource_setup_request(const asn1::ngap::pdu_session_res_setup_request_s& request) { - ue_index_t ue_index = ue_manager.get_ue_index(uint_to_ran_ue_id(request->ran_ue_ngap_id)); - ngap_ue* ue = ue_manager.find_ngap_ue(ue_index); - if (ue == nullptr) { - logger.warning("ue={} ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceSetupRequest. UE does not exist", - ue_index, + if (!ue_ctxt_list.contains(uint_to_ran_ue_id(request->ran_ue_ngap_id))) { + logger.warning("ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceSetupRequest. UE context does not exist", request->ran_ue_ngap_id, request->amf_ue_ngap_id); - send_error_indication(ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + send_error_indication(ue_index_t::invalid, cause_radio_network_t::unknown_local_ue_ngap_id); return; } + ngap_ue_context& ue_ctxt = ue_ctxt_list[uint_to_ran_ue_id(request->ran_ue_ngap_id)]; + + if (ue_ctxt.release_scheduled) { + logger.info( + "ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceSetupRequest. UE is already scheduled for release", + request->ran_ue_ngap_id, + request->amf_ue_ngap_id); + schedule_error_indication(ue_ctxt.ue_ids.ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + return; + } + + ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); + srsran_assert(ue != nullptr, + "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + if (!ue->get_rrc_ue_control_notifier().on_security_enabled()) { logger.warning( "ue={} ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceSetupRequest. Security context does not exist", - ue_index, + ue_ctxt.ue_ids.ue_index, request->ran_ue_ngap_id, request->amf_ue_ngap_id); - send_error_indication(ue_index); + send_error_indication(ue_ctxt.ue_ids.ue_index); return; } logger.info("ue={} ran_ue_id={} amf_ue_id={}: Received PduSessionResourceSetupRequest", - ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); // Store information in UE context if (request->ue_aggr_max_bit_rate_present) { - ue->set_aggregate_maximum_bit_rate_dl(request->ue_aggr_max_bit_rate.ue_aggr_max_bit_rate_dl); + ue_ctxt.aggregate_maximum_bit_rate_dl = request->ue_aggr_max_bit_rate.ue_aggr_max_bit_rate_dl; } // Convert to common type cu_cp_pdu_session_resource_setup_request msg; - msg.ue_index = ue_index; + msg.ue_index = ue_ctxt.ue_ids.ue_index; msg.serving_plmn = context.plmn; if (!fill_cu_cp_pdu_session_resource_setup_request(msg, request->pdu_session_res_setup_list_su_req)) { logger.error("ue={} ran_ue_id={} amf_ue_id={}: Conversion of PDU Session Resource Setup Request failed", - ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); - send_error_indication(ue_index); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + send_error_indication(ue_ctxt.ue_ids.ue_index); return; } - msg.ue_aggregate_maximum_bit_rate_dl = ue->get_aggregate_maximum_bit_rate_dl(); + msg.ue_aggregate_maximum_bit_rate_dl = ue_ctxt.aggregate_maximum_bit_rate_dl; // start routine task_sched.schedule_async_task( - ue_index, - launch_async( - msg, request->nas_pdu.copy(), *ue, ue->get_du_processor_control_notifier(), ngap_notifier, logger)); + ue_ctxt.ue_ids.ue_index, + launch_async(msg, + request->nas_pdu.copy(), + ue_ctxt.ue_ids, + ue->get_rrc_ue_pdu_notifier(), + ue->get_du_processor_control_notifier(), + ngap_notifier, + logger)); } void ngap_impl::handle_pdu_session_resource_modify_request(const asn1::ngap::pdu_session_res_modify_request_s& request) { - ue_index_t ue_index = ue_manager.get_ue_index(uint_to_ran_ue_id(request->ran_ue_ngap_id)); - ngap_ue* ue = ue_manager.find_ngap_ue(ue_index); - if (ue == nullptr) { - logger.warning("ue={} ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceModifyRequest. UE does not exist", - ue_index, + if (!ue_ctxt_list.contains(uint_to_ran_ue_id(request->ran_ue_ngap_id))) { + logger.warning("ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceModifyRequest. UE context does not exist", request->ran_ue_ngap_id, request->amf_ue_ngap_id); - send_error_indication(ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + send_error_indication(ue_index_t::invalid, cause_radio_network_t::unknown_local_ue_ngap_id); return; } + ngap_ue_context& ue_ctxt = ue_ctxt_list[uint_to_ran_ue_id(request->ran_ue_ngap_id)]; + if (ue_ctxt.release_scheduled) { + logger.info( + "ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceModifyRequest. UE is already scheduled for release", + request->ran_ue_ngap_id, + request->amf_ue_ngap_id); + schedule_error_indication(ue_ctxt.ue_ids.ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + return; + } + + ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); + srsran_assert(ue != nullptr, + "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + logger.info("ue={} ran_ue_id={} amf_ue_id={}: Received PduSessionResourceModifyRequest", - ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); if (request->ran_paging_prio_present) { logger.debug("Not handling RAN paging prio"); @@ -372,32 +476,48 @@ void ngap_impl::handle_pdu_session_resource_modify_request(const asn1::ngap::pdu // Convert to common type cu_cp_pdu_session_resource_modify_request msg; - msg.ue_index = ue_index; + msg.ue_index = ue_ctxt.ue_ids.ue_index; fill_cu_cp_pdu_session_resource_modify_request(msg, request->pdu_session_res_modify_list_mod_req); // start routine - task_sched.schedule_async_task(ue_index, - launch_async( - msg, *ue, ue->get_du_processor_control_notifier(), ngap_notifier, logger)); + task_sched.schedule_async_task( + ue_ctxt.ue_ids.ue_index, + launch_async( + msg, ue_ctxt.ue_ids, ue->get_du_processor_control_notifier(), ngap_notifier, logger)); } void ngap_impl::handle_pdu_session_resource_release_command(const asn1::ngap::pdu_session_res_release_cmd_s& command) { - ue_index_t ue_index = ue_manager.get_ue_index(uint_to_ran_ue_id(command->ran_ue_ngap_id)); - ngap_ue* ue = ue_manager.find_ngap_ue(ue_index); - if (ue == nullptr) { - logger.warning("ue={} ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceReleaseCommand. UE does not exist", - ue_index, + if (!ue_ctxt_list.contains(uint_to_ran_ue_id(command->ran_ue_ngap_id))) { + logger.warning("ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceReleaseCommand. UE context does not exist", command->ran_ue_ngap_id, command->amf_ue_ngap_id); - send_error_indication(ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + send_error_indication(ue_index_t::invalid, cause_radio_network_t::unknown_local_ue_ngap_id); + return; + } + + ngap_ue_context& ue_ctxt = ue_ctxt_list[uint_to_ran_ue_id(command->ran_ue_ngap_id)]; + + if (ue_ctxt.release_scheduled) { + logger.info( + "ran_ue_id={} amf_ue_id={}: Dropping PduSessionResourceReleaseCommand. UE is already scheduled for release", + command->ran_ue_ngap_id, + command->amf_ue_ngap_id); + schedule_error_indication(ue_ctxt.ue_ids.ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); return; } + ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); + srsran_assert(ue != nullptr, + "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + logger.info("ue={} ran_ue_id={} amf_ue_id={}: Received PduSessionResourceReleaseCommand", - ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); // Handle optional NAS PDU if (command->nas_pdu_present) { @@ -409,64 +529,82 @@ void ngap_impl::handle_pdu_session_resource_release_command(const asn1::ngap::pd ue->get_rrc_ue_pdu_notifier().on_new_pdu(std::move(nas_pdu)); } + // Convert to common type cu_cp_pdu_session_resource_release_command msg; - msg.ue_index = ue_index; + msg.ue_index = ue_ctxt.ue_ids.ue_index; fill_cu_cp_pdu_session_resource_release_command(msg, command); // start routine - task_sched.schedule_async_task(ue_index, - launch_async( - *ue, msg, ue->get_du_processor_control_notifier(), ngap_notifier, logger)); + task_sched.schedule_async_task( + ue_ctxt.ue_ids.ue_index, + launch_async( + msg, ue_ctxt.ue_ids, ue->get_du_processor_control_notifier(), ngap_notifier, logger)); } void ngap_impl::handle_ue_context_release_command(const asn1::ngap::ue_context_release_cmd_s& cmd) { amf_ue_id_t amf_ue_id = amf_ue_id_t::invalid; ran_ue_id_t ran_ue_id = ran_ue_id_t::invalid; - ue_index_t ue_index = ue_index_t::invalid; if (cmd->ue_ngap_ids.type() == asn1::ngap::ue_ngap_ids_c::types_opts::amf_ue_ngap_id) { amf_ue_id = uint_to_amf_ue_id(cmd->ue_ngap_ids.amf_ue_ngap_id()); - ue_index = ue_manager.get_ue_index(amf_ue_id); } else if (cmd->ue_ngap_ids.type() == asn1::ngap::ue_ngap_ids_c::types_opts::ue_ngap_id_pair) { amf_ue_id = uint_to_amf_ue_id(cmd->ue_ngap_ids.ue_ngap_id_pair().amf_ue_ngap_id); ran_ue_id = uint_to_ran_ue_id(cmd->ue_ngap_ids.ue_ngap_id_pair().ran_ue_ngap_id); - ue_index = ue_manager.get_ue_index(ran_ue_id); } - auto* ue = ue_manager.find_ngap_ue(ue_index); - if (ue == nullptr) { - // TS 38.413 section 8.3.3 doesn't specify abnormal conditions, so we just drop the message - logger.warning("ue={}{} amf_ue_id={}: Dropping UeContextReleaseCommand. UE does not exist", - ue_index, - ran_ue_id == ran_ue_id_t::invalid ? "" : fmt::format(" ran_ue_id={}", ran_ue_id), - amf_ue_id); - send_error_indication(ue_index, cause_radio_network_t::unknown_local_ue_ngap_id); + if (!ue_ctxt_list.contains(amf_ue_id)) { + // TS 38.413 section 8.3.3 doesn't specify abnormal conditions, so we just drop the message and send an error + // indication + logger.error("{}amf_ue_id={}: Dropping UeContextReleaseCommand. UE does not exist", + ran_ue_id == ran_ue_id_t::invalid ? "" : fmt::format("ran_ue_id={} ", ran_ue_id), + amf_ue_id); + send_error_indication(ue_index_t::invalid, cause_radio_network_t::unknown_local_ue_ngap_id, amf_ue_id); return; } + ngap_ue_context& ue_ctxt = ue_ctxt_list[amf_ue_id]; + + if (ue_ctxt.release_scheduled) { + logger.info("{}amf_ue_id={}: Dropping UeContextReleaseCommand. UE is already scheduled for release", + ran_ue_id == ran_ue_id_t::invalid ? "" : fmt::format("ran_ue_id={} ", ran_ue_id), + amf_ue_id); + schedule_error_indication(ue_ctxt.ue_ids.ue_index, cause_radio_network_t::unknown_local_ue_ngap_id, amf_ue_id); + return; + } else { + ue_ctxt.release_scheduled = true; + } + if (ran_ue_id == ran_ue_id_t::invalid) { - ran_ue_id = ue->get_ran_ue_id(); + ran_ue_id = ue_ctxt.ue_ids.ran_ue_id; } // Add AMF UE ID to UE, if its not set - if (ue->get_amf_ue_id() == amf_ue_id_t::invalid) { - ue_manager.set_amf_ue_id(ue_index, amf_ue_id); + if (ue_ctxt.ue_ids.amf_ue_id == amf_ue_id_t::invalid) { + ue_ctxt_list.add_amf_ue_id(ran_ue_id, amf_ue_id); } + ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); + srsran_assert(ue != nullptr, + "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + logger.info("ue={} ran_ue_id={} amf_ue_id={}: Received UeContextReleaseCommand", - ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); // Convert to common type cu_cp_ngap_ue_context_release_command msg; - msg.ue_index = ue_index; + msg.ue_index = ue_ctxt.ue_ids.ue_index; fill_cu_cp_ngap_ue_context_release_command(msg, cmd); // start routine - task_sched.schedule_async_task(ue_index, - launch_async( - msg, ue->get_du_processor_control_notifier(), ngap_notifier, ue_manager, logger)); + task_sched.schedule_async_task( + ue_ctxt.ue_ids.ue_index, + launch_async( + msg, ue_ctxt.ue_ids, ue->get_du_processor_control_notifier(), ngap_notifier, logger)); } void ngap_impl::handle_paging(const asn1::ngap::paging_s& msg) @@ -529,47 +667,43 @@ void ngap_impl::handle_ho_request(const asn1::ngap::ho_request_s& msg) ho_request.ue_index, launch_async(ho_request, uint_to_amf_ue_id(msg->amf_ue_ngap_id), + ue_ctxt_list, cu_cp_du_repository_notifier, ngap_notifier, - ue_manager, logger)); } void ngap_impl::handle_error_indication(const asn1::ngap::error_ind_s& msg) { - ue_index_t ue_index = ue_index_t::invalid; - ngap_ue* ue = nullptr; - cause_t cause; + amf_ue_id_t amf_ue_id = amf_ue_id_t::invalid; + ran_ue_id_t ran_ue_id = ran_ue_id_t::invalid; + ue_index_t ue_index = ue_index_t::invalid; if (msg->amf_ue_ngap_id_present) { - ue_index = ue_manager.get_ue_index(uint_to_amf_ue_id(msg->amf_ue_ngap_id)); - ue = ue_manager.find_ngap_ue(ue_index); - cause = cause_radio_network_t::inconsistent_remote_ue_ngap_id; + amf_ue_id = uint_to_amf_ue_id(msg->amf_ue_ngap_id); + if (!ue_ctxt_list.contains(uint_to_amf_ue_id(msg->amf_ue_ngap_id))) { + logger.warning("amf_ue_id={}: Dropping ErrorIndication. UE context does not exist", msg->amf_ue_ngap_id); + send_error_indication(ue_index_t::invalid, cause_radio_network_t::inconsistent_remote_ue_ngap_id); + return; + } + ue_index = ue_ctxt_list[amf_ue_id].ue_ids.ue_index; } else if (msg->ran_ue_ngap_id_present) { - ue_index = ue_manager.get_ue_index(uint_to_ran_ue_id(msg->ran_ue_ngap_id)); - ue = ue_manager.find_ngap_ue(ue_index); - cause = cause_radio_network_t::unknown_local_ue_ngap_id; - } - - if (ue == nullptr) { - logger.warning("ue={}{}{}: Dropping ErrorIndication. UE does not exist", - ue_index, - msg->ran_ue_ngap_id_present ? fmt::format(" ran_ue_id={}", msg->ran_ue_ngap_id) : "", - msg->amf_ue_ngap_id_present ? fmt::format(" amf_ue_id={}", msg->amf_ue_ngap_id) : ""); - send_error_indication(ue_index, cause); - return; - } else { - std::string msg_cause = ""; - if (msg->cause_present) { - msg_cause = asn1_cause_to_string(msg->cause); + ran_ue_id = uint_to_ran_ue_id(msg->ran_ue_ngap_id); + if (!ue_ctxt_list.contains(uint_to_ran_ue_id(msg->ran_ue_ngap_id))) { + logger.warning("ran_ue_id={}: Dropping ErrorIndication. UE context does not exist", msg->ran_ue_ngap_id); + send_error_indication(ue_index_t::invalid, cause_radio_network_t::unknown_local_ue_ngap_id); + return; } + ue_index = ue_ctxt_list[ran_ue_id].ue_ids.ue_index; + } - logger.info("ue={} ran_ue_id={} amf_ue_id={}: Received ErrorIndication. Cause {}", - ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id(), - msg_cause); + std::string msg_cause = ""; + if (msg->cause_present) { + msg_cause = asn1_cause_to_string(msg->cause); } + logger.info( + "ue={} ran_ue_id={} amf_ue_id={}: Received ErrorIndication. Cause {}", ue_index, ran_ue_id, amf_ue_id, msg_cause); + // TODO: handle error indication } @@ -603,16 +737,24 @@ void ngap_impl::handle_unsuccessful_outcome(const unsuccessful_outcome_s& outcom void ngap_impl::handle_ue_context_release_request(const cu_cp_ue_context_release_request& msg) { - ngap_ue* ue = ue_manager.find_ngap_ue(msg.ue_index); - if (ue == nullptr) { - logger.warning("ue={}: Dropping UeContextReleaseRequest. UE does not exist", msg.ue_index); + if (!ue_ctxt_list.contains(msg.ue_index)) { + logger.warning("ue={}: Dropping UeContextReleaseRequest. UE context does not exist", msg.ue_index); return; } - if (ue->get_amf_ue_id() == amf_ue_id_t::invalid) { + ngap_ue_context& ue_ctxt = ue_ctxt_list[msg.ue_index]; + + if (ue_ctxt.ue_ids.amf_ue_id == amf_ue_id_t::invalid) { logger.debug("ue={} ran_ue_id={}: Ignoring UeContextReleaseRequest. UE does not have an AMF UE ID", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id); + return; + } + + if (ue_ctxt.release_requested) { + logger.debug("ue={} ran_ue_id={}: Ignoring UeContextReleaseRequest. Request already pending", msg.ue_index, - ue->get_ran_ue_id()); + ue_ctxt.ue_ids.ran_ue_id); return; } @@ -622,8 +764,8 @@ void ngap_impl::handle_ue_context_release_request(const cu_cp_ue_context_release auto& ue_context_release_request = ngap_msg.pdu.init_msg().value.ue_context_release_request(); - ue_context_release_request->ran_ue_ngap_id = ran_ue_id_to_uint(ue->get_ran_ue_id()); - ue_context_release_request->amf_ue_ngap_id = amf_ue_id_to_uint(ue->get_amf_ue_id()); + ue_context_release_request->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ctxt.ue_ids.ran_ue_id); + ue_context_release_request->amf_ue_ngap_id = amf_ue_id_to_uint(ue_ctxt.ue_ids.amf_ue_id); // Add PDU Session IDs if (!msg.pdu_session_res_list_cxt_rel_req.empty()) { @@ -642,9 +784,10 @@ void ngap_impl::handle_ue_context_release_request(const cu_cp_ue_context_release // Forward message to AMF logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending UeContextReleaseRequest", - msg.ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + ue_ctxt.release_requested = true; // Mark UE so retx of request are avoided. ngap_notifier.on_new_message(ngap_msg); } @@ -652,25 +795,34 @@ void ngap_impl::handle_ue_context_release_request(const cu_cp_ue_context_release async_task ngap_impl::handle_handover_preparation_request(const ngap_handover_preparation_request& msg) { - auto* ue = ue_manager.find_ngap_ue(msg.ue_index); - if (ue == nullptr) { - logger.warning("ue={}: Dropping handover preparation request. UE does not exist", msg.ue_index); - + if (!ue_ctxt_list.contains(msg.ue_index)) { + logger.warning("ue={}: Dropping HandoverPreparationRequest. UE context does not exist", msg.ue_index); return launch_async([](coro_context>& ctx) { CORO_BEGIN(ctx); CORO_RETURN(ngap_handover_preparation_response{false}); }); } + ngap_ue_context& ue_ctxt = ue_ctxt_list[msg.ue_index]; + + ngap_ue* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); + srsran_assert(ue != nullptr, + "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + logger.info("ue={} ran_ue_id={} amf_ue_id={}: Starting HO preparation", - msg.ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + return launch_async(msg, context, - ue, + ue_ctxt.ue_ids, ngap_notifier, ue->get_rrc_ue_control_notifier(), + ue->get_up_resource_manager(), ev_mng, timer_factory{task_sched.get_timer_manager(), ctrl_exec}, logger); @@ -680,19 +832,20 @@ void ngap_impl::handle_inter_cu_ho_rrc_recfg_complete(const ue_index_t const nr_cell_global_id_t& cgi, const unsigned tac) { - auto* ue = ue_manager.find_ngap_ue(ue_index); - if (ue == nullptr) { - logger.warning("ue={}: Dropping RrcReconfigurationComplete. UE does not exist", ue_index); + if (!ue_ctxt_list.contains(ue_index)) { + logger.warning("ue={}: Dropping RrcReconfigurationComplete. UE context does not exist", ue_index); return; } + ngap_ue_context& ue_ctxt = ue_ctxt_list[ue_index]; + ngap_message ngap_msg = {}; ngap_msg.pdu.set_init_msg(); ngap_msg.pdu.init_msg().load_info_obj(ASN1_NGAP_ID_HO_NOTIF); auto& ho_notify = ngap_msg.pdu.init_msg().value.ho_notify(); - ho_notify->ran_ue_ngap_id = ran_ue_id_to_uint(ue->get_ran_ue_id()); - ho_notify->amf_ue_ngap_id = amf_ue_id_to_uint(ue->get_amf_ue_id()); + ho_notify->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ctxt.ue_ids.ran_ue_id); + ho_notify->amf_ue_ngap_id = amf_ue_id_to_uint(ue_ctxt.ue_ids.amf_ue_id); auto& user_loc_info_nr = ho_notify->user_location_info.set_user_location_info_nr(); user_loc_info_nr.nr_cgi = nr_cgi_to_ngap_asn1(cgi); @@ -700,17 +853,24 @@ void ngap_impl::handle_inter_cu_ho_rrc_recfg_complete(const ue_index_t user_loc_info_nr.tai.tac.from_number(tac); // Forward message to AMF - logger.info( - "ue={} ran_ue_id={} amf_ue_id={}: Sending HandoverNotify", ue_index, ue->get_ran_ue_id(), ue->get_amf_ue_id()); + logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending HandoverNotify", + ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); ngap_notifier.on_new_message(ngap_msg); } -size_t ngap_impl::get_nof_ues() const +void ngap_impl::remove_ue_context(ue_index_t ue_index) { - return ue_manager.get_nof_ngap_ues(); + if (!ue_ctxt_list.contains(ue_index)) { + logger.debug("ue={}: UE context not found", ue_index); + return; + } + + ue_ctxt_list.remove_ue_context(ue_index); } -void ngap_impl::send_error_indication(ue_index_t ue_index, optional cause) +void ngap_impl::send_error_indication(ue_index_t ue_index, optional cause, optional amf_ue_id) { ngap_message ngap_msg = {}; ngap_msg.pdu.set_init_msg(); @@ -718,18 +878,25 @@ void ngap_impl::send_error_indication(ue_index_t ue_index, optional cau auto& error_ind = ngap_msg.pdu.init_msg().value.error_ind(); if (ue_index != ue_index_t::invalid) { - auto* ue = ue_manager.find_ngap_ue(ue_index); - if (ue != nullptr) { + if (ue_ctxt_list.contains(ue_index)) { + ngap_ue_context& ue_ctxt = ue_ctxt_list[ue_index]; + error_ind->ran_ue_ngap_id_present = true; - error_ind->ran_ue_ngap_id = ran_ue_id_to_uint(ue->get_ran_ue_id()); + error_ind->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ctxt.ue_ids.ran_ue_id); - if (ue->get_amf_ue_id() != amf_ue_id_t::invalid) { + if (ue_ctxt.ue_ids.amf_ue_id != amf_ue_id_t::invalid) { error_ind->amf_ue_ngap_id_present = true; - error_ind->amf_ue_ngap_id = amf_ue_id_to_uint(ue->get_amf_ue_id()); + error_ind->amf_ue_ngap_id = amf_ue_id_to_uint(ue_ctxt.ue_ids.amf_ue_id); } } } + // Set optionally provided AMF ID + if (amf_ue_id.has_value()) { + error_ind->amf_ue_ngap_id_present = true; + error_ind->amf_ue_ngap_id = amf_ue_id_to_uint(amf_ue_id.value()); + } + if (cause.has_value()) { error_ind->cause_present = true; error_ind->cause = cause_to_asn1(cause.value()); @@ -744,3 +911,46 @@ void ngap_impl::send_error_indication(ue_index_t ue_index, optional cau error_ind->amf_ue_ngap_id_present ? fmt::format(" amf_ue_id={}", error_ind->amf_ue_ngap_id) : ""); ngap_notifier.on_new_message(ngap_msg); } + +void ngap_impl::schedule_error_indication(ue_index_t ue_index, cause_t cause, optional amf_ue_id) +{ + logger.info("{}{}: Scheduling ErrorIndication", + ue_index != ue_index_t::invalid ? fmt::format("ue={}", ue_index) : "", + amf_ue_id.has_value() ? fmt::format(" amf_ue_id={}", amf_ue_id.value()) : ""); + task_sched.schedule_async_task(ue_index, + launch_async([this, ue_index, cause, amf_ue_id](coro_context>& ctx) { + CORO_BEGIN(ctx); + send_error_indication(ue_index, cause, amf_ue_id); + CORO_RETURN(); + })); +} + +void ngap_impl::on_ue_context_setup_timer_expired(ue_index_t ue_index) +{ + if (ue_ctxt_list.contains(ue_index)) { + ngap_ue_context& ue_ctxt = ue_ctxt_list[ue_index]; + + logger.warning("ue={}: UE context setup timer expired after {}s. Releasing UE from DU", + ue_index, + ue_ctxt.ue_context_setup_timer.duration().count()); + + auto* ue = ue_manager.find_ngap_ue(ue_ctxt.ue_ids.ue_index); + srsran_assert(ue != nullptr, + "ue={} ran_ue_id={} amf_ue_id={}: UE for UE context doesn't exist", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); + + task_sched.schedule_async_task(ue_index, launch_async([ue, ue_index](coro_context>& ctx) { + CORO_BEGIN(ctx); + CORO_AWAIT( + ue->get_du_processor_control_notifier().on_new_ue_context_release_command( + {ue_index, cause_nas_t::unspecified})); + CORO_RETURN(); + })); + + } else { + logger.debug("ue={}: Ignoring expired UE context setup timer. UE context not found", ue_index); + return; + } +} diff --git a/lib/ngap/ngap_impl.h b/lib/ngap/ngap_impl.h index db25387cb8..1902ef09b3 100644 --- a/lib/ngap/ngap_impl.h +++ b/lib/ngap/ngap_impl.h @@ -24,6 +24,7 @@ #include "ngap_context.h" #include "procedures/ngap_transaction_manager.h" +#include "ue_context/ngap_ue_context.h" #include "srsran/asn1/ngap/ngap.h" #include "srsran/cu_cp/ue_manager.h" #include "srsran/ngap/ngap.h" @@ -52,12 +53,14 @@ class ngap_impl final : public ngap_interface ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier, ngap_du_processor_control_notifier& du_processor_ctrl_notifier) override; + bool update_ue_index(ue_index_t new_ue_index, ue_index_t old_ue_index) override; + // ngap connection manager functions async_task handle_ng_setup_request(const ng_setup_request& request) override; - void handle_initial_ue_message(const ngap_initial_ue_message& msg) override; + void handle_initial_ue_message(const cu_cp_initial_ue_message& msg) override; - void handle_ul_nas_transport_message(const ngap_ul_nas_transport_message& msg) override; + void handle_ul_nas_transport_message(const cu_cp_ul_nas_transport& msg) override; // ngap message handler functions void handle_message(const ngap_message& msg) override; @@ -71,16 +74,20 @@ class ngap_impl final : public ngap_interface const nr_cell_global_id_t& cgi, const unsigned tac) override; - // ngap_statistic_interface - size_t get_nof_ues() const override; + // ngap_statistics_handler + size_t get_nof_ues() const override { return ue_ctxt_list.size(); } + + // ngap_ue_context_removal_handler + void remove_ue_context(ue_index_t ue_index) override; - ngap_message_handler& get_ngap_message_handler() override { return *this; } - ngap_event_handler& get_ngap_event_handler() override { return *this; } - ngap_connection_manager& get_ngap_connection_manager() override { return *this; } - ngap_nas_message_handler& get_ngap_nas_message_handler() override { return *this; } - ngap_control_message_handler& get_ngap_control_message_handler() override { return *this; } - ngap_ue_control_manager& get_ngap_ue_control_manager() override { return *this; } - ngap_statistic_interface& get_ngap_statistic_interface() override { return *this; } + ngap_message_handler& get_ngap_message_handler() override { return *this; } + ngap_event_handler& get_ngap_event_handler() override { return *this; } + ngap_connection_manager& get_ngap_connection_manager() override { return *this; } + ngap_nas_message_handler& get_ngap_nas_message_handler() override { return *this; } + ngap_control_message_handler& get_ngap_control_message_handler() override { return *this; } + ngap_ue_control_manager& get_ngap_ue_control_manager() override { return *this; } + ngap_statistics_handler& get_ngap_statistics_handler() override { return *this; } + ngap_ue_context_removal_handler& get_ngap_ue_context_removal_handler() override { return *this; } private: /// \brief Notify about the reception of an initiating message. @@ -134,12 +141,26 @@ class ngap_impl final : public ngap_interface /// \brief Send an Error Indication message to the core. /// \param[in] ue_index The index of the related UE. /// \param[in] cause The cause of the Error Indication. - /// \param[in] five_g_s_tmsi The 5G S TMSI. - void send_error_indication(ue_index_t ue_index = ue_index_t::invalid, optional cause = {}); + /// \param[in] amf_ue_id The AMF UE ID. + void send_error_indication(ue_index_t ue_index = ue_index_t::invalid, + optional cause = {}, + optional amf_ue_id = {}); + + /// \brief Schedule the transmission of an Error Indication message on the UE task executor. + /// \param[in] ue_index The index of the related UE. + /// \param[in] cause The cause of the Error Indication. + /// \param[in] amf_ue_id The AMF UE ID. + void schedule_error_indication(ue_index_t ue_index, cause_t cause, optional amf_ue_id = {}); + + void on_ue_context_setup_timer_expired(ue_index_t ue_index); ngap_context_t context; - srslog::basic_logger& logger; + srslog::basic_logger& logger; + + /// Repository of UE Contexts. + ngap_ue_context_list ue_ctxt_list; + ngap_cu_cp_du_repository_notifier& cu_cp_du_repository_notifier; ngap_ue_task_scheduler& task_sched; ngap_ue_manager& ue_manager; diff --git a/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.cpp b/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.cpp new file mode 100644 index 0000000000..99b4c3b2e2 --- /dev/null +++ b/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.cpp @@ -0,0 +1,60 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "ngap_dl_nas_message_transfer_procedure.h" + +using namespace srsran; +using namespace srsran::srs_cu_cp; +using namespace asn1::ngap; + +ngap_dl_nas_message_transfer_procedure::ngap_dl_nas_message_transfer_procedure( + byte_buffer nas_pdu_, + const ngap_ue_ids& ue_ids_, + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_, + srslog::basic_logger& logger_) : + nas_pdu(nas_pdu_), ue_ids(ue_ids_), rrc_ue_pdu_notifier(rrc_ue_pdu_notifier_), logger(logger_) +{ +} + +void ngap_dl_nas_message_transfer_procedure::operator()(coro_context>& ctx) +{ + CORO_BEGIN(ctx); + + logger.debug("ue={} ran_ue_id={} amf_ue_id={}: \"{}\" initialized", + ue_ids.ue_index, + ue_ids.ran_ue_id, + ue_ids.amf_ue_id, + name()); + + send_pdu_to_rrc_ue(); + + logger.debug( + "ue={} ran_ue_id={} amf_ue_id={}: \"{}\" finalized", ue_ids.ue_index, ue_ids.ran_ue_id, ue_ids.amf_ue_id, name()); + + CORO_RETURN(); +} + +void ngap_dl_nas_message_transfer_procedure::send_pdu_to_rrc_ue() +{ + logger.debug(nas_pdu.begin(), nas_pdu.end(), "DlNasTransport PDU ({} B)", nas_pdu.length()); + rrc_ue_pdu_notifier.on_new_pdu(std::move(nas_pdu)); +} diff --git a/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.h b/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.h new file mode 100644 index 0000000000..daa4d86d8f --- /dev/null +++ b/lib/ngap/procedures/ngap_dl_nas_message_transfer_procedure.h @@ -0,0 +1,55 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "../ue_context/ngap_ue_context.h" +#include "srsran/ngap/ngap.h" +#include "srsran/support/async/async_task.h" + +namespace srsran { +namespace srs_cu_cp { + +class ngap_dl_nas_message_transfer_procedure +{ +public: + ngap_dl_nas_message_transfer_procedure(byte_buffer nas_pdu_, + const ngap_ue_ids& ue_ids_, + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_, + srslog::basic_logger& logger_); + + void operator()(coro_context>& ctx); + + static const char* name() { return "DL NAS Message Transfer Procedure"; } + +private: + // results senders + void send_pdu_to_rrc_ue(); + + byte_buffer nas_pdu; + const ngap_ue_ids ue_ids; + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier; + srslog::basic_logger& logger; +}; + +} // namespace srs_cu_cp +} // namespace srsran diff --git a/lib/ngap/procedures/ngap_handover_preparation_procedure.cpp b/lib/ngap/procedures/ngap_handover_preparation_procedure.cpp index 553ba4f88f..3ab9b6b297 100644 --- a/lib/ngap/procedures/ngap_handover_preparation_procedure.cpp +++ b/lib/ngap/procedures/ngap_handover_preparation_procedure.cpp @@ -29,18 +29,20 @@ using namespace asn1::ngap; ngap_handover_preparation_procedure::ngap_handover_preparation_procedure( const ngap_handover_preparation_request& request_, - ngap_context_t& context_, - ngap_ue* ue_, + const ngap_context_t& context_, + const ngap_ue_ids& ue_ids_, ngap_message_notifier& amf_notif_, ngap_rrc_ue_control_notifier& rrc_ue_notif_, + up_resource_manager& up_manager_, ngap_transaction_manager& ev_mng_, timer_factory timers, srslog::basic_logger& logger_) : request(request_), context(context_), - ue(ue_), + ue_ids(ue_ids_), amf_notifier(amf_notif_), rrc_ue_notifier(rrc_ue_notif_), + up_manager(up_manager_), ev_mng(ev_mng_), logger(logger_), tng_reloc_prep_timer(timers.create_timer()) @@ -53,28 +55,30 @@ void ngap_handover_preparation_procedure::operator()(coro_contextget_amf_ue_id() == amf_ue_id_t::invalid || ue->get_ran_ue_id() == ran_ue_id_t::invalid) { + if (ue_ids.amf_ue_id == amf_ue_id_t::invalid || ue_ids.ran_ue_id == ran_ue_id_t::invalid) { logger.error( - "ue={} ran_id={} amf_id={}: invalid NGAP id pair", request.ue_index, ue->get_ran_ue_id(), ue->get_amf_ue_id()); + "ue={} ran_id={} amf_id={}: Invalid NGAP id pair", request.ue_index, ue_ids.ran_ue_id, ue_ids.amf_ue_id); CORO_EARLY_RETURN(ngap_handover_preparation_response{false}); } // Subscribe to respective publisher to receive HANDOVER COMMAND/HANDOVER PREPARATION FAILURE message. transaction_sink.subscribe_to(ev_mng.handover_preparation_outcome, tng_reloc_prep_ms); - // Get required context from UE RRC - ho_ue_context = ue->get_rrc_ue_control_notifier().on_ue_source_handover_context_required(); + // Get required context from RRC UE + get_required_handover_context(); + + // Send Handover Required to AMF send_handover_required(); CORO_AWAIT(transaction_sink); if (transaction_sink.timeout_expired()) { - logger.debug("ue={} ran_id={} amf_id={}: \"{}\" timed out after {}ms", - request.ue_index, - ue->get_ran_ue_id(), - ue->get_amf_ue_id(), - name(), - tng_reloc_prep_ms.count()); + logger.warning("ue={} ran_id={} amf_id={}: \"{}\" timed out after {}ms", + request.ue_index, + ue_ids.ran_ue_id, + ue_ids.amf_ue_id, + name(), + tng_reloc_prep_ms.count()); // TODO: Initialize Handover Cancellation procedure } @@ -90,6 +94,23 @@ void ngap_handover_preparation_procedure::operator()(coro_context& pdu_sessions = up_manager.get_pdu_sessions_map(); + // create a map of all PDU sessions and their associated QoS flows + for (const auto& pdu_session : pdu_sessions) { + std::vector qos_flows; + for (const auto& drb : pdu_session.second.drbs) { + for (const auto& qos_flow : drb.second.qos_flows) { + qos_flows.push_back(qos_flow.first); + } + } + ho_ue_context.pdu_sessions.insert({pdu_session.first, qos_flows}); + } + ho_ue_context.rrc_container = rrc_ue_notifier.on_handover_preparation_message_required(); +} + void ngap_handover_preparation_procedure::send_handover_required() { ngap_message msg = {}; @@ -98,8 +119,8 @@ void ngap_handover_preparation_procedure::send_handover_required() msg.pdu.init_msg().load_info_obj(ASN1_NGAP_ID_HO_PREP); ho_required_s& ho_required = msg.pdu.init_msg().value.ho_required(); - ho_required->amf_ue_ngap_id = amf_ue_id_to_uint(ue->get_amf_ue_id()); - ho_required->ran_ue_ngap_id = ran_ue_id_to_uint(ue->get_ran_ue_id()); + ho_required->amf_ue_ngap_id = amf_ue_id_to_uint(ue_ids.amf_ue_id); + ho_required->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ids.ran_ue_id); // only intra5gs supported. ho_required->handov_type = handov_type_opts::intra5gs; diff --git a/lib/ngap/procedures/ngap_handover_preparation_procedure.h b/lib/ngap/procedures/ngap_handover_preparation_procedure.h index bae8fa1cff..ae2da2e915 100644 --- a/lib/ngap/procedures/ngap_handover_preparation_procedure.h +++ b/lib/ngap/procedures/ngap_handover_preparation_procedure.h @@ -23,6 +23,7 @@ #pragma once #include "../ngap_context.h" +#include "../ue_context/ngap_ue_context.h" #include "ngap_transaction_manager.h" #include "srsran/cu_cp/ue_manager.h" #include "srsran/ngap/ngap.h" @@ -35,10 +36,11 @@ class ngap_handover_preparation_procedure { public: ngap_handover_preparation_procedure(const ngap_handover_preparation_request& req_, - ngap_context_t& context_, - ngap_ue* ue_, + const ngap_context_t& context_, + const ngap_ue_ids& ue_ids_, ngap_message_notifier& amf_notif_, ngap_rrc_ue_control_notifier& rrc_ue_notif_, + up_resource_manager& up_manager_, ngap_transaction_manager& ev_mng_, timer_factory timers, srslog::basic_logger& logger_); @@ -49,10 +51,11 @@ class ngap_handover_preparation_procedure private: const ngap_handover_preparation_request request; - ngap_context_t& context; - ngap_ue* ue = nullptr; + const ngap_context_t context; + const ngap_ue_ids ue_ids; ngap_message_notifier& amf_notifier; ngap_rrc_ue_control_notifier& rrc_ue_notifier; + up_resource_manager& up_manager; ngap_transaction_manager& ev_mng; srslog::basic_logger& logger; @@ -62,6 +65,7 @@ class ngap_handover_preparation_procedure protocol_transaction_outcome_observer transaction_sink; + void get_required_handover_context(); void send_handover_required(); bool forward_rrc_handover_command(); diff --git a/lib/ngap/procedures/ngap_handover_resource_allocation_procedure.cpp b/lib/ngap/procedures/ngap_handover_resource_allocation_procedure.cpp index 3be5e4931e..625995261d 100644 --- a/lib/ngap/procedures/ngap_handover_resource_allocation_procedure.cpp +++ b/lib/ngap/procedures/ngap_handover_resource_allocation_procedure.cpp @@ -30,15 +30,15 @@ using namespace asn1::ngap; ngap_handover_resource_allocation_procedure::ngap_handover_resource_allocation_procedure( const ngap_handover_request& request_, const amf_ue_id_t amf_ue_id_, + ngap_ue_context_list& ue_ctxt_list_, ngap_cu_cp_du_repository_notifier& du_repository_notif_, ngap_message_notifier& amf_notif_, - ngap_ue_manager& ue_manager_, srslog::basic_logger& logger_) : request(request_), amf_ue_id(amf_ue_id_), + ue_ctxt_list(ue_ctxt_list_), du_repository_notifier(du_repository_notif_), amf_notifier(amf_notif_), - ue_manager(ue_manager_), logger(logger_) { } @@ -54,11 +54,10 @@ void ngap_handover_resource_allocation_procedure::operator()(coro_contextamf_ue_ngap_id = amf_ue_id_to_uint(ue->get_amf_ue_id()); - ho_request_ack->ran_ue_ngap_id = ran_ue_id_to_uint(ue->get_ran_ue_id()); + ho_request_ack->amf_ue_ngap_id = amf_ue_id_to_uint(amf_ue_id); + ho_request_ack->ran_ue_ngap_id = ran_ue_id_to_uint(ran_ue_id); - logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending HoRequestAck", - ue->get_ue_index(), - ue->get_ran_ue_id(), - ue->get_amf_ue_id()); + logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending HoRequestAck", ue_index, ran_ue_id, amf_ue_id); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_handover_resource_allocation_procedure.h b/lib/ngap/procedures/ngap_handover_resource_allocation_procedure.h index c955211bf7..3156683051 100644 --- a/lib/ngap/procedures/ngap_handover_resource_allocation_procedure.h +++ b/lib/ngap/procedures/ngap_handover_resource_allocation_procedure.h @@ -22,7 +22,7 @@ #pragma once -#include "srsran/cu_cp/ue_manager.h" // for ngap_ue +#include "../ue_context/ngap_ue_context.h" #include "srsran/ngap/ngap.h" #include "srsran/ngap/ngap_handover.h" #include "srsran/support/async/async_task.h" @@ -35,9 +35,9 @@ class ngap_handover_resource_allocation_procedure public: ngap_handover_resource_allocation_procedure(const ngap_handover_request& request_, const amf_ue_id_t amf_ue_id_, + ngap_ue_context_list& ue_ctxt_list_, ngap_cu_cp_du_repository_notifier& du_repository_notif_, ngap_message_notifier& amf_notif_, - ngap_ue_manager& ue_manager_, srslog::basic_logger& logger_); void operator()(coro_context>& ctx); @@ -46,18 +46,16 @@ class ngap_handover_resource_allocation_procedure private: // results senders - void send_handover_request_ack(); + void send_handover_request_ack(ue_index_t ue_index, ran_ue_id_t ran_ue_id); void send_handover_failure(); const ngap_handover_request& request; const amf_ue_id_t amf_ue_id; + ngap_ue_context_list& ue_ctxt_list; ngap_cu_cp_du_repository_notifier& du_repository_notifier; ngap_message_notifier& amf_notifier; - ngap_ue_manager& ue_manager; srslog::basic_logger& logger; - ngap_ue* ue = nullptr; - // (sub-)routine requests // (sub-)routine results diff --git a/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp b/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp index abdbbbad96..6d4d36e71a 100644 --- a/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp +++ b/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp @@ -23,6 +23,7 @@ #include "ngap_initial_context_setup_procedure.h" #include "../ngap_asn1_helpers.h" #include "ngap_procedure_helpers.h" +#include "srsran/ngap/ngap.h" #include "srsran/ran/cause.h" using namespace srsran; @@ -30,21 +31,21 @@ using namespace srsran::srs_cu_cp; using namespace asn1::ngap; ngap_initial_context_setup_procedure::ngap_initial_context_setup_procedure( - ngap_context_t& context_, - const ue_index_t ue_index_, - const asn1::ngap::init_context_setup_request_s& request_, - ngap_ue_manager& ue_manager_, - ngap_message_notifier& amf_notif_, - srslog::basic_logger& logger_) : - context(context_), - ue_index(ue_index_), + const ngap_init_context_setup_request& request_, + const ngap_ue_context& ue_ctxt_, + ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier_, + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_, + ngap_du_processor_control_notifier& du_processor_ctrl_notifier_, + ngap_message_notifier& amf_notifier_, + srslog::basic_logger& logger_) : request(request_), - ue_manager(ue_manager_), - amf_notifier(amf_notif_), + ue_ctxt(ue_ctxt_), + rrc_ue_ctrl_notifier(rrc_ue_ctrl_notifier_), + rrc_ue_pdu_notifier(rrc_ue_pdu_notifier_), + du_processor_ctrl_notifier(du_processor_ctrl_notifier_), + amf_notifier(amf_notifier_), logger(logger_) { - ue = ue_manager.find_ngap_ue(ue_index); - srsran_assert(ue != nullptr, "ue={}: UE does not exist", ue_index); } void ngap_initial_context_setup_procedure::operator()(coro_context>& ctx) @@ -52,98 +53,83 @@ void ngap_initial_context_setup_procedure::operator()(coro_contextget_amf_ue_id(), - ue->get_ran_ue_id(), + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id, name()); // Handle mandatory IEs - CORO_AWAIT_VALUE( - success, - ue->get_rrc_ue_control_notifier().on_new_security_context(request->ue_security_cap, request->security_key)); + CORO_AWAIT_VALUE(success, rrc_ue_ctrl_notifier.on_new_security_context(request.security_context)); if (not success) { - fail_msg.cause.set_protocol(); + fail_msg.cause = cause_protocol_t::unspecified; // Add failed PDU Sessions - if (request->pdu_session_res_setup_list_cxt_req_present) { - for (const auto& pdu_session_item : request->pdu_session_res_setup_list_cxt_req) { + if (request.pdu_session_res_setup_list_cxt_req.has_value()) { + for (const auto& pdu_session_item : + request.pdu_session_res_setup_list_cxt_req.value().pdu_session_res_setup_items) { cu_cp_pdu_session_res_setup_failed_item failed_item; - failed_item.pdu_session_id = uint_to_pdu_session_id(pdu_session_item.pdu_session_id); + failed_item.pdu_session_id = pdu_session_item.pdu_session_id; failed_item.unsuccessful_transfer.cause = cause_radio_network_t::unspecified; - fail_msg.pdu_session_res_failed_to_setup_items.emplace(uint_to_pdu_session_id(pdu_session_item.pdu_session_id), - failed_item); + fail_msg.pdu_session_res_failed_to_setup_items.emplace(pdu_session_item.pdu_session_id, failed_item); } } - send_initial_context_setup_failure(fail_msg, ue->get_amf_ue_id(), ue->get_ran_ue_id()); + send_initial_context_setup_failure(fail_msg, ue_ctxt.ue_ids.amf_ue_id, ue_ctxt.ue_ids.ran_ue_id); - logger.debug( - "ue={} ran_ue_id={} amf_ue_id={}: \"{}\" failed", ue_index, ue->get_amf_ue_id(), ue->get_ran_ue_id(), name()); - - // Remove NGAP UE (DU UE was already released at SMC procedure) - ue_manager.remove_ngap_ue(ue_index); + logger.debug("ue={} ran_ue_id={} amf_ue_id={}: \"{}\" failed", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id, + name()); CORO_EARLY_RETURN(); } - // Handle GUAMI - context.current_guami = asn1_to_guami(request->guami); - // Handle optional IEs // Handle PDU Session Resource Setup List Context Request - if (request->pdu_session_res_setup_list_cxt_req_present) { - // Handle UE Aggregate Maximum Bitrate - if (request->ue_aggr_max_bit_rate_present) { - ue->set_aggregate_maximum_bit_rate_dl(request->ue_aggr_max_bit_rate.ue_aggr_max_bit_rate_dl); - } - - // Convert to common type - pdu_session_setup_request.ue_index = ue_index; - pdu_session_setup_request.serving_plmn = request->guami.plmn_id.to_string(); - if (!fill_cu_cp_pdu_session_resource_setup_request(pdu_session_setup_request, - request->pdu_session_res_setup_list_cxt_req)) { - logger.error("ue={} ran_ue_id={} amf_ue_id={}: Conversion of PDU Session Resource Setup Request failed.", - ue_index, - ue->get_amf_ue_id(), - ue->get_ran_ue_id()); - CORO_EARLY_RETURN(); - } - pdu_session_setup_request.ue_aggregate_maximum_bit_rate_dl = ue->get_aggregate_maximum_bit_rate_dl(); + if (request.pdu_session_res_setup_list_cxt_req.has_value()) { + request.pdu_session_res_setup_list_cxt_req.value().ue_index = ue_ctxt.ue_ids.ue_index; + request.pdu_session_res_setup_list_cxt_req.value().serving_plmn = request.guami.plmn; + request.pdu_session_res_setup_list_cxt_req.value().ue_aggregate_maximum_bit_rate_dl = + ue_ctxt.aggregate_maximum_bit_rate_dl; // Handle mandatory IEs - CORO_AWAIT_VALUE( - pdu_session_response, - ue->get_du_processor_control_notifier().on_new_pdu_session_resource_setup_request(pdu_session_setup_request)); + CORO_AWAIT_VALUE(pdu_session_response, + du_processor_ctrl_notifier.on_new_pdu_session_resource_setup_request( + request.pdu_session_res_setup_list_cxt_req.value())); // Handle NAS PDUs - for (const auto& session : request->pdu_session_res_setup_list_cxt_req) { - if (!session.nas_pdu.empty()) { - handle_nas_pdu(logger, session.nas_pdu, *ue); + for (auto& session : request.pdu_session_res_setup_list_cxt_req.value().pdu_session_res_setup_items) { + if (!session.pdu_session_nas_pdu.empty()) { + handle_nas_pdu(logger, std::move(session.pdu_session_nas_pdu), rrc_ue_pdu_notifier); } } } - if (request->nas_pdu_present) { - handle_nas_pdu(logger, request->nas_pdu, *ue); + if (request.nas_pdu.has_value()) { + handle_nas_pdu(logger, std::move(request.nas_pdu.value()), rrc_ue_pdu_notifier); } resp_msg.pdu_session_res_setup_response_items = pdu_session_response.pdu_session_res_setup_response_items; resp_msg.pdu_session_res_failed_to_setup_items = pdu_session_response.pdu_session_res_failed_to_setup_items; - send_initial_context_setup_response(resp_msg, ue->get_amf_ue_id(), ue->get_ran_ue_id()); + send_initial_context_setup_response(resp_msg, ue_ctxt.ue_ids.amf_ue_id, ue_ctxt.ue_ids.ran_ue_id); - logger.debug( - "ue={} ran_ue_id={} amf_ue_id={}: \"{}\" finalized", ue_index, ue->get_amf_ue_id(), ue->get_ran_ue_id(), name()); + logger.debug("ue={} ran_ue_id={} amf_ue_id={}: \"{}\" finalized", + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id, + name()); CORO_RETURN(); } void ngap_initial_context_setup_procedure::send_initial_context_setup_response( - const ngap_initial_context_response_message& msg, - const amf_ue_id_t& amf_ue_id, - const ran_ue_id_t& ran_ue_id) + const ngap_init_context_setup_response& msg, + const amf_ue_id_t& amf_ue_id, + const ran_ue_id_t& ran_ue_id) { ngap_message ngap_msg = {}; @@ -156,16 +142,16 @@ void ngap_initial_context_setup_procedure::send_initial_context_setup_response( fill_asn1_initial_context_setup_response(init_ctxt_setup_resp, msg); logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending InitialContextSetupResponse", - ue_index, - ue->get_amf_ue_id(), - ue->get_ran_ue_id()); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); amf_notifier.on_new_message(ngap_msg); } void ngap_initial_context_setup_procedure::send_initial_context_setup_failure( - const ngap_initial_context_failure_message& msg, - const amf_ue_id_t& amf_ue_id, - const ran_ue_id_t& ran_ue_id) + const ngap_init_context_setup_failure& msg, + const amf_ue_id_t& amf_ue_id, + const ran_ue_id_t& ran_ue_id) { ngap_message ngap_msg = {}; @@ -179,8 +165,8 @@ void ngap_initial_context_setup_procedure::send_initial_context_setup_failure( fill_asn1_initial_context_setup_failure(init_ctxt_setup_fail, msg); logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending InitialContextSetupFailure", - ue_index, - ue->get_amf_ue_id(), - ue->get_ran_ue_id()); + ue_ctxt.ue_ids.ue_index, + ue_ctxt.ue_ids.ran_ue_id, + ue_ctxt.ue_ids.amf_ue_id); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_initial_context_setup_procedure.h b/lib/ngap/procedures/ngap_initial_context_setup_procedure.h index 743fa45aaf..70f288db20 100644 --- a/lib/ngap/procedures/ngap_initial_context_setup_procedure.h +++ b/lib/ngap/procedures/ngap_initial_context_setup_procedure.h @@ -22,10 +22,9 @@ #pragma once -#include "../ngap_asn1_utils.h" -#include "../ngap_context.h" -#include "srsran/cu_cp/ue_manager.h" // for ngap_ue +#include "../ue_context/ngap_ue_context.h" #include "srsran/ngap/ngap.h" +#include "srsran/ngap/ngap_init_context_setup.h" #include "srsran/support/async/async_task.h" namespace srsran { @@ -34,12 +33,13 @@ namespace srs_cu_cp { class ngap_initial_context_setup_procedure { public: - ngap_initial_context_setup_procedure(ngap_context_t& context_, - const ue_index_t ue_index_, - const asn1::ngap::init_context_setup_request_s& request_, - ngap_ue_manager& ue_manager_, - ngap_message_notifier& amf_notif_, - srslog::basic_logger& logger_); + ngap_initial_context_setup_procedure(const ngap_init_context_setup_request& request_, + const ngap_ue_context& ue_ctxt_, + ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier_, + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_, + ngap_du_processor_control_notifier& du_processor_ctrl_notifier_, + ngap_message_notifier& amf_notifier_, + srslog::basic_logger& logger_); void operator()(coro_context>& ctx); @@ -47,28 +47,27 @@ class ngap_initial_context_setup_procedure private: // results senders - void send_initial_context_setup_response(const ngap_initial_context_response_message& msg, - const amf_ue_id_t& amf_ue_id, - const ran_ue_id_t& ran_ue_id); - void send_initial_context_setup_failure(const ngap_initial_context_failure_message& msg, - const amf_ue_id_t& amf_ue_id, - const ran_ue_id_t& ran_ue_id); + void send_initial_context_setup_response(const ngap_init_context_setup_response& msg, + const amf_ue_id_t& amf_ue_id, + const ran_ue_id_t& ran_ue_id); + void send_initial_context_setup_failure(const ngap_init_context_setup_failure& msg, + const amf_ue_id_t& amf_ue_id, + const ran_ue_id_t& ran_ue_id); - ngap_context_t& context; - const ue_index_t ue_index; - const asn1::ngap::init_context_setup_request_s request; - cu_cp_pdu_session_resource_setup_request pdu_session_setup_request; - cu_cp_pdu_session_resource_setup_response pdu_session_response; - ngap_ue_manager& ue_manager; - ngap_message_notifier& amf_notifier; - srslog::basic_logger& logger; + ngap_init_context_setup_request request; + const ngap_ue_context& ue_ctxt; + ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier; + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier; + ngap_du_processor_control_notifier& du_processor_ctrl_notifier; + ngap_message_notifier& amf_notifier; + srslog::basic_logger& logger; - ngap_ue* ue = nullptr; + cu_cp_pdu_session_resource_setup_response pdu_session_response; // (sub-)routine results - ngap_initial_context_failure_message fail_msg; + ngap_init_context_setup_failure fail_msg; cu_cp_ngap_ue_context_release_command rel_cmd; - ngap_initial_context_response_message resp_msg; + ngap_init_context_setup_response resp_msg; bool success = false; }; diff --git a/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.cpp b/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.cpp index 209a78e106..32589f9ad7 100644 --- a/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.cpp +++ b/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.cpp @@ -29,12 +29,12 @@ using namespace asn1::ngap; ngap_pdu_session_resource_modify_procedure::ngap_pdu_session_resource_modify_procedure( const cu_cp_pdu_session_resource_modify_request& request_, - ngap_ue& ue_, + const ngap_ue_ids& ue_ids_, ngap_du_processor_control_notifier& du_processor_ctrl_notif_, ngap_message_notifier& amf_notif_, srslog::basic_logger& logger_) : request(request_), - ue(ue_), + ue_ids(ue_ids_), du_processor_ctrl_notifier(du_processor_ctrl_notif_), amf_notifier(amf_notif_), logger(logger_) @@ -46,9 +46,9 @@ void ngap_pdu_session_resource_modify_procedure::operator()(coro_contextamf_ue_ngap_id = amf_ue_id_to_uint(ue.get_amf_ue_id()); - pdu_session_res_setup_resp->ran_ue_ngap_id = ran_ue_id_to_uint(ue.get_ran_ue_id()); + pdu_session_res_setup_resp->amf_ue_ngap_id = amf_ue_id_to_uint(ue_ids.amf_ue_id); + pdu_session_res_setup_resp->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ids.ran_ue_id); logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending PduSessionResourceModifyResponse", - ue.get_ue_index(), - ue.get_amf_ue_id(), - ue.get_ran_ue_id()); + ue_ids.ue_index, + ue_ids.amf_ue_id, + ue_ids.ran_ue_id); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.h b/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.h index 3c46e6706e..25e0e41568 100644 --- a/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.h +++ b/lib/ngap/procedures/ngap_pdu_session_resource_modify_procedure.h @@ -23,8 +23,9 @@ #pragma once #include "../ngap_asn1_utils.h" -#include "srsran/cu_cp/ue_manager.h" // for ngap_ue +#include "../ue_context/ngap_ue_context.h" #include "srsran/ngap/ngap.h" +#include "srsran/ngap/ngap_types.h" #include "srsran/support/async/async_task.h" namespace srsran { @@ -34,7 +35,7 @@ class ngap_pdu_session_resource_modify_procedure { public: ngap_pdu_session_resource_modify_procedure(const cu_cp_pdu_session_resource_modify_request& request_, - ngap_ue& ue_, + const ngap_ue_ids& ue_ids_, ngap_du_processor_control_notifier& du_processor_ctrl_notif_, ngap_message_notifier& amf_notif_, srslog::basic_logger& logger_); @@ -48,7 +49,7 @@ class ngap_pdu_session_resource_modify_procedure void send_pdu_session_resource_modify_response(); cu_cp_pdu_session_resource_modify_request request; - ngap_ue& ue; + const ngap_ue_ids ue_ids; cu_cp_pdu_session_resource_modify_response response; ngap_du_processor_control_notifier& du_processor_ctrl_notifier; ngap_message_notifier& amf_notifier; diff --git a/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.cpp b/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.cpp index 5eb382d738..d306796c27 100644 --- a/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.cpp +++ b/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.cpp @@ -28,13 +28,13 @@ using namespace srsran::srs_cu_cp; using namespace asn1::ngap; ngap_pdu_session_resource_release_procedure::ngap_pdu_session_resource_release_procedure( - ngap_ue& ue_, const cu_cp_pdu_session_resource_release_command& command_, + const ngap_ue_ids& ue_ids_, ngap_du_processor_control_notifier& du_processor_ctrl_notif_, ngap_message_notifier& amf_notif_, srslog::basic_logger& logger_) : - ue(ue_), command(command_), + ue_ids(ue_ids_), du_processor_ctrl_notifier(du_processor_ctrl_notif_), amf_notifier(amf_notif_), logger(logger_) @@ -46,9 +46,9 @@ void ngap_pdu_session_resource_release_procedure::operator()(coro_contextamf_ue_ngap_id = amf_ue_id_to_uint(ue.get_amf_ue_id()); - pdu_session_res_release_resp->ran_ue_ngap_id = ran_ue_id_to_uint(ue.get_ran_ue_id()); + pdu_session_res_release_resp->amf_ue_ngap_id = amf_ue_id_to_uint(ue_ids.amf_ue_id); + pdu_session_res_release_resp->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ids.ran_ue_id); logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending PduSessionResourceReleaseResponse", - ue.get_ue_index(), - ue.get_amf_ue_id(), - ue.get_ran_ue_id()); + ue_ids.ue_index, + ue_ids.amf_ue_id, + ue_ids.ran_ue_id); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.h b/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.h index 95d9d98806..f4411540c3 100644 --- a/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.h +++ b/lib/ngap/procedures/ngap_pdu_session_resource_release_procedure.h @@ -23,7 +23,7 @@ #pragma once #include "../ngap_asn1_utils.h" -#include "srsran/cu_cp/ue_manager.h" // for ngap_ue +#include "../ue_context/ngap_ue_context.h" #include "srsran/ngap/ngap.h" #include "srsran/support/async/async_task.h" @@ -33,8 +33,8 @@ namespace srs_cu_cp { class ngap_pdu_session_resource_release_procedure { public: - ngap_pdu_session_resource_release_procedure(ngap_ue& ue_, - const cu_cp_pdu_session_resource_release_command& command_, + ngap_pdu_session_resource_release_procedure(const cu_cp_pdu_session_resource_release_command& command_, + const ngap_ue_ids& ue_ids_, ngap_du_processor_control_notifier& du_processor_ctrl_notif_, ngap_message_notifier& amf_notif_, srslog::basic_logger& logger_); @@ -47,8 +47,8 @@ class ngap_pdu_session_resource_release_procedure // results senders void send_pdu_session_resource_release_response(); - ngap_ue& ue; cu_cp_pdu_session_resource_release_command command; + const ngap_ue_ids ue_ids; cu_cp_pdu_session_resource_release_response response; ngap_du_processor_control_notifier& du_processor_ctrl_notifier; ngap_message_notifier& amf_notifier; diff --git a/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.cpp b/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.cpp index 81447fe619..46cc96cab4 100644 --- a/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.cpp +++ b/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.cpp @@ -23,6 +23,7 @@ #include "ngap_pdu_session_resource_setup_procedure.h" #include "../ngap/ngap_asn1_helpers.h" #include "ngap_procedure_helpers.h" +#include "srsran/ngap/ngap.h" using namespace srsran; using namespace srsran::srs_cu_cp; @@ -31,13 +32,15 @@ using namespace asn1::ngap; ngap_pdu_session_resource_setup_procedure::ngap_pdu_session_resource_setup_procedure( const cu_cp_pdu_session_resource_setup_request& request_, byte_buffer nas_pdu_, - ngap_ue& ue_, + const ngap_ue_ids& ue_ids_, + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_, ngap_du_processor_control_notifier& du_processor_ctrl_notif_, ngap_message_notifier& amf_notif_, srslog::basic_logger& logger_) : request(request_), nas_pdu(nas_pdu_), - ue(ue_), + ue_ids(ue_ids_), + rrc_ue_pdu_notifier(rrc_ue_pdu_notifier_), du_processor_ctrl_notifier(du_processor_ctrl_notif_), amf_notifier(amf_notif_), logger(logger_) @@ -49,9 +52,9 @@ void ngap_pdu_session_resource_setup_procedure::operator()(coro_contextamf_ue_ngap_id = amf_ue_id_to_uint(ue.get_amf_ue_id()); - pdu_session_res_setup_resp->ran_ue_ngap_id = ran_ue_id_to_uint(ue.get_ran_ue_id()); + pdu_session_res_setup_resp->amf_ue_ngap_id = amf_ue_id_to_uint(ue_ids.amf_ue_id); + pdu_session_res_setup_resp->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ids.ran_ue_id); logger.info("ue={} ran_ue_id={} amf_ue_id={}: Sending PduSessionResourceSetupResponse", - ue.get_ue_index(), - ue.get_amf_ue_id(), - ue.get_ran_ue_id()); + ue_ids.ue_index, + ue_ids.amf_ue_id, + ue_ids.ran_ue_id); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.h b/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.h index 66e94fad6e..e19596f905 100644 --- a/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.h +++ b/lib/ngap/procedures/ngap_pdu_session_resource_setup_procedure.h @@ -23,7 +23,7 @@ #pragma once #include "../ngap_asn1_utils.h" -#include "srsran/cu_cp/ue_manager.h" // for ngap_ue +#include "../ue_context/ngap_ue_context.h" #include "srsran/ngap/ngap.h" #include "srsran/support/async/async_task.h" @@ -35,7 +35,8 @@ class ngap_pdu_session_resource_setup_procedure public: ngap_pdu_session_resource_setup_procedure(const cu_cp_pdu_session_resource_setup_request& request_, byte_buffer nas_pdu_, - ngap_ue& ue_, + const ngap_ue_ids& ue_ids_, + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_, ngap_du_processor_control_notifier& du_processor_ctrl_notif_, ngap_message_notifier& amf_notif_, srslog::basic_logger& logger_); @@ -50,7 +51,8 @@ class ngap_pdu_session_resource_setup_procedure cu_cp_pdu_session_resource_setup_request request; byte_buffer nas_pdu; - ngap_ue& ue; + const ngap_ue_ids ue_ids; + ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier; cu_cp_pdu_session_resource_setup_response response; ngap_du_processor_control_notifier& du_processor_ctrl_notifier; ngap_message_notifier& amf_notifier; diff --git a/lib/ngap/procedures/ngap_procedure_helpers.h b/lib/ngap/procedures/ngap_procedure_helpers.h index e0dda1c3de..a3a8b31cd4 100644 --- a/lib/ngap/procedures/ngap_procedure_helpers.h +++ b/lib/ngap/procedures/ngap_procedure_helpers.h @@ -22,19 +22,16 @@ #pragma once -#include "srsran/cu_cp/ue_manager.h" // for ngap_ue #include "srsran/ngap/ngap.h" namespace srsran { namespace srs_cu_cp { -inline void handle_nas_pdu(srslog::basic_logger& logger, const asn1::unbounded_octstring& nas_pdu, ngap_ue& ue) +inline void +handle_nas_pdu(srslog::basic_logger& logger, byte_buffer nas_pdu, ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier) { logger.debug("Forwarding NAS PDU to RRC"); - byte_buffer rrc_nas_pdu; - rrc_nas_pdu.resize(nas_pdu.size()); - std::copy(nas_pdu.begin(), nas_pdu.end(), rrc_nas_pdu.begin()); - ue.get_rrc_ue_pdu_notifier().on_new_pdu(std::move(rrc_nas_pdu)); + rrc_ue_pdu_notifier.on_new_pdu(std::move(nas_pdu)); } } // namespace srs_cu_cp diff --git a/lib/ngap/procedures/ngap_ue_context_release_procedure.cpp b/lib/ngap/procedures/ngap_ue_context_release_procedure.cpp index a4cc6f81c8..9a43189d47 100644 --- a/lib/ngap/procedures/ngap_ue_context_release_procedure.cpp +++ b/lib/ngap/procedures/ngap_ue_context_release_procedure.cpp @@ -29,14 +29,14 @@ using namespace asn1::ngap; ngap_ue_context_release_procedure::ngap_ue_context_release_procedure( const cu_cp_ngap_ue_context_release_command& command_, - ngap_du_processor_control_notifier& du_processor_ctrl_notif_, - ngap_message_notifier& amf_notif_, - ngap_ue_manager& ue_manager_, + const ngap_ue_ids& ue_ids_, + ngap_du_processor_control_notifier& du_processor_ctrl_notifier_, + ngap_message_notifier& amf_notifier_, srslog::basic_logger& logger_) : command(command_), - du_processor_ctrl_notifier(du_processor_ctrl_notif_), - amf_notifier(amf_notif_), - ue_manager(ue_manager_), + ue_ids(ue_ids_), + du_processor_ctrl_notifier(du_processor_ctrl_notifier_), + amf_notifier(amf_notifier_), logger(logger_) { } @@ -45,14 +45,22 @@ void ngap_ue_context_release_procedure::operator()(coro_context { CORO_BEGIN(ctx); - logger.debug("ue={}: \"{}\" initialized", command.ue_index, name()); + logger.debug("ue={}: \"{}\" initialized", ue_ids.ue_index, name()); // Notify DU processor about UE Context Release Command CORO_AWAIT_VALUE(ue_context_release_complete, du_processor_ctrl_notifier.on_new_ue_context_release_command(command)); + // Verify response from DU processor. + if (ue_context_release_complete.ue_index != command.ue_index) { + logger.debug("ue={}: \"{}\" aborted. UE does not exist anymore", command.ue_index, name()); + CORO_EARLY_RETURN(); + } + + // Note: From this point the UE is removed and only the stored context can be accessed. + send_ue_context_release_complete(); - logger.debug("ue={}: \"{}\" finalized", ue_context_release_complete.ue_index, name()); + logger.debug("ue={}: \"{}\" finalized", ue_ids.ue_index, name()); CORO_RETURN(); } @@ -64,17 +72,12 @@ void ngap_ue_context_release_procedure::send_ue_context_release_complete() ngap_msg.pdu.successful_outcome().load_info_obj(ASN1_NGAP_ID_UE_CONTEXT_RELEASE); auto& asn1_ue_context_release_complete = ngap_msg.pdu.successful_outcome().value.ue_context_release_complete(); - asn1_ue_context_release_complete->amf_ue_ngap_id = - amf_ue_id_to_uint(ue_manager.find_ngap_ue(ue_context_release_complete.ue_index)->get_amf_ue_id()); - asn1_ue_context_release_complete->ran_ue_ngap_id = - ran_ue_id_to_uint(ue_manager.find_ngap_ue(ue_context_release_complete.ue_index)->get_ran_ue_id()); + asn1_ue_context_release_complete->amf_ue_ngap_id = amf_ue_id_to_uint(ue_ids.amf_ue_id); + asn1_ue_context_release_complete->ran_ue_ngap_id = ran_ue_id_to_uint(ue_ids.ran_ue_id); fill_asn1_ue_context_release_complete(asn1_ue_context_release_complete, ue_context_release_complete); - // Remove NGAP UE - ue_manager.remove_ngap_ue(ue_context_release_complete.ue_index); - - logger.info("ue={}: Sending UeContextReleaseComplete", ue_context_release_complete.ue_index); + logger.info("ue={}: Sending UeContextReleaseComplete", ue_ids.ue_index); amf_notifier.on_new_message(ngap_msg); } diff --git a/lib/ngap/procedures/ngap_ue_context_release_procedure.h b/lib/ngap/procedures/ngap_ue_context_release_procedure.h index 0ecdaf00bc..3973eb6257 100644 --- a/lib/ngap/procedures/ngap_ue_context_release_procedure.h +++ b/lib/ngap/procedures/ngap_ue_context_release_procedure.h @@ -22,7 +22,7 @@ #pragma once -#include "srsran/cu_cp/ue_manager.h" +#include "../ue_context/ngap_ue_context.h" #include "srsran/ngap/ngap.h" #include "srsran/support/async/async_task.h" @@ -33,9 +33,9 @@ class ngap_ue_context_release_procedure { public: ngap_ue_context_release_procedure(const cu_cp_ngap_ue_context_release_command& command_, - ngap_du_processor_control_notifier& du_processor_ctrl_notif_, - ngap_message_notifier& amf_notif_, - ngap_ue_manager& ue_manager_, + const ngap_ue_ids& ue_ids_, + ngap_du_processor_control_notifier& du_processor_ctrl_notifier_, + ngap_message_notifier& amf_notifier_, srslog::basic_logger& logger_); void operator()(coro_context>& ctx); @@ -47,10 +47,10 @@ class ngap_ue_context_release_procedure void send_ue_context_release_complete(); cu_cp_ngap_ue_context_release_command command; + const ngap_ue_ids ue_ids; cu_cp_ue_context_release_complete ue_context_release_complete; ngap_du_processor_control_notifier& du_processor_ctrl_notifier; ngap_message_notifier& amf_notifier; - ngap_ue_manager& ue_manager; srslog::basic_logger& logger; }; diff --git a/lib/ngap/ue_context/ngap_ue_context.h b/lib/ngap/ue_context/ngap_ue_context.h new file mode 100644 index 0000000000..aef08e5ff4 --- /dev/null +++ b/lib/ngap/ue_context/ngap_ue_context.h @@ -0,0 +1,254 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/ngap/ngap.h" +#include "srsran/ngap/ngap_types.h" +#include + +namespace srsran { +namespace srs_cu_cp { + +struct ngap_ue_ids { + ue_index_t ue_index = ue_index_t::invalid; + ran_ue_id_t ran_ue_id = ran_ue_id_t::invalid; + amf_ue_id_t amf_ue_id = amf_ue_id_t::invalid; +}; + +struct ngap_ue_context { + ngap_ue_ids ue_ids; + uint64_t aggregate_maximum_bit_rate_dl = 0; + unique_timer ue_context_setup_timer = {}; + bool release_requested = false; + bool release_scheduled = false; + + ngap_ue_context(ue_index_t ue_index_, ran_ue_id_t ran_ue_id_, timer_manager& timers_, task_executor& task_exec_) + { + ue_ids = {ue_index_, ran_ue_id_}; + ue_context_setup_timer = timers_.create_unique_timer(task_exec_); + } +}; + +class ngap_ue_context_list +{ +public: + ngap_ue_context_list(srslog::basic_logger& logger_) : logger(logger_) {} + + /// \brief Checks whether a UE with the given RAN UE ID exists. + /// \param[in] ran_ue_id The RAN UE ID used to find the UE. + /// \return True when a UE for the given RAN UE ID exists, false otherwise. + bool contains(ran_ue_id_t ran_ue_id) const { return ues.find(ran_ue_id) != ues.end(); } + + /// \brief Checks whether a UE with the given UE index exists. + /// \param[in] ue_index The UE index used to find the UE. + /// \return True when a UE for the given UE index exists, false otherwise. + bool contains(ue_index_t ue_index) const + { + if (ue_index_to_ran_ue_id.find(ue_index) == ue_index_to_ran_ue_id.end()) { + return false; + } + if (ues.find(ue_index_to_ran_ue_id.at(ue_index)) == ues.end()) { + return false; + } + return true; + } + + /// \brief Checks whether a UE with the given AMF UE ID exists. + /// \param[in] amf_ue_id The AMF UE ID used to find the UE. + /// \return True when a UE for the given AMF UE ID exists, false otherwise. + bool contains(amf_ue_id_t amf_ue_id) const + { + if (amf_ue_id_to_ran_ue_id.find(amf_ue_id) == amf_ue_id_to_ran_ue_id.end()) { + return false; + } + if (ues.find(amf_ue_id_to_ran_ue_id.at(amf_ue_id)) == ues.end()) { + return false; + } + return true; + } + + ngap_ue_context& operator[](ran_ue_id_t ran_ue_id) + { + srsran_assert(ues.find(ran_ue_id) != ues.end(), "ran_ue_id={}: NGAP UE context not found", ran_ue_id); + return ues.at(ran_ue_id); + } + + ngap_ue_context& operator[](ue_index_t ue_index) + { + srsran_assert( + ue_index_to_ran_ue_id.find(ue_index) != ue_index_to_ran_ue_id.end(), "ue={}: RAN-UE-ID not found", ue_index); + srsran_assert(ues.find(ue_index_to_ran_ue_id.at(ue_index)) != ues.end(), + "ran_ue_id={}: NGAP UE context not found", + ue_index_to_ran_ue_id.at(ue_index)); + return ues.at(ue_index_to_ran_ue_id.at(ue_index)); + } + + ngap_ue_context& operator[](amf_ue_id_t amf_ue_id) + { + srsran_assert(amf_ue_id_to_ran_ue_id.find(amf_ue_id) != amf_ue_id_to_ran_ue_id.end(), + "amf_ue_id={}: RAN-UE-ID not found", + amf_ue_id); + srsran_assert(ues.find(amf_ue_id_to_ran_ue_id.at(amf_ue_id)) != ues.end(), + "ran_ue_id={}: NGAP UE context not found", + amf_ue_id_to_ran_ue_id.at(amf_ue_id)); + return ues.at(amf_ue_id_to_ran_ue_id.at(amf_ue_id)); + } + + ngap_ue_context& add_ue(ue_index_t ue_index, ran_ue_id_t ran_ue_id, timer_manager& timers, task_executor& task_exec) + { + srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); + srsran_assert(ran_ue_id != ran_ue_id_t::invalid, "Invalid ran_ue_id={}", ran_ue_id); + + logger.debug("ue={} ran_ue_id={}: Adding NGAP UE context", ue_index, ran_ue_id); + ues.emplace(std::piecewise_construct, + std::forward_as_tuple(ran_ue_id), + std::forward_as_tuple(ue_index, ran_ue_id, timers, task_exec)); + ue_index_to_ran_ue_id.emplace(ue_index, ran_ue_id); + return ues.at(ran_ue_id); + } + + void add_amf_ue_id(ran_ue_id_t ran_ue_id, amf_ue_id_t amf_ue_id) + { + srsran_assert(amf_ue_id != amf_ue_id_t::invalid, "Invalid amf_ue_id={}", amf_ue_id); + srsran_assert(ran_ue_id != ran_ue_id_t::invalid, "Invalid ran_ue_id={}", ran_ue_id); + srsran_assert(ues.find(ran_ue_id) != ues.end(), "ran_ue_id={}: NGAP UE context not found", ran_ue_id); + + logger.debug("ue={} ran_ue_id={}: Adding amf_ue_id={}", ues.at(ran_ue_id).ue_ids.ue_index, ran_ue_id, amf_ue_id); + ues.at(ran_ue_id).ue_ids.amf_ue_id = amf_ue_id; + amf_ue_id_to_ran_ue_id.emplace(amf_ue_id, ran_ue_id); + } + + void update_ue_index(ue_index_t new_ue_index, ue_index_t old_ue_index) + { + srsran_assert(new_ue_index != ue_index_t::invalid, "Invalid new_ue_index={}", new_ue_index); + srsran_assert(old_ue_index != ue_index_t::invalid, "Invalid old_ue_index={}", old_ue_index); + srsran_assert(ue_index_to_ran_ue_id.find(old_ue_index) != ue_index_to_ran_ue_id.end(), + "ue={}: RAN-UE-ID not found", + old_ue_index); + + ran_ue_id_t ran_ue_id = ue_index_to_ran_ue_id.at(old_ue_index); + + srsran_assert(ues.find(ran_ue_id) != ues.end(), "ran_ue_id={}: NGAP UE context not found", ran_ue_id); + + // Update UE context + ues.at(ran_ue_id).ue_ids.ue_index = new_ue_index; + + // Update lookups + ue_index_to_ran_ue_id.emplace(new_ue_index, ran_ue_id); + ue_index_to_ran_ue_id.erase(old_ue_index); + + logger.debug("ran_ue_id={} amf_ue_id={}: Updated UE index from ue_index={} to ue_index={}", + ran_ue_id, + ues.at(ran_ue_id).ue_ids.amf_ue_id, + old_ue_index, + new_ue_index); + } + + void remove_ue_context(ue_index_t ue_index) + { + srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); + + if (ue_index_to_ran_ue_id.find(ue_index) == ue_index_to_ran_ue_id.end()) { + logger.warning("ue={}: RAN-UE-ID not found", ue_index); + return; + } + + // Remove UE from lookup + ran_ue_id_t ran_ue_id = ue_index_to_ran_ue_id.at(ue_index); + ue_index_to_ran_ue_id.erase(ue_index); + + if (ues.find(ran_ue_id) == ues.end()) { + logger.warning("ran_ue_id={}: NGAP UE context not found", ran_ue_id); + return; + } + + logger.debug("ue={} ran_ue_id={}{}: Removing NGAP UE context", + ue_index, + ran_ue_id, + ues.at(ran_ue_id).ue_ids.amf_ue_id == amf_ue_id_t::invalid ? "" : " amf_ue_id={}", + ues.at(ran_ue_id).ue_ids.amf_ue_id); + + if (ues.at(ran_ue_id).ue_ids.amf_ue_id != amf_ue_id_t::invalid) { + amf_ue_id_to_ran_ue_id.erase(ues.at(ran_ue_id).ue_ids.amf_ue_id); + } + ues.erase(ran_ue_id); + } + + size_t size() const { return ues.size(); } + + /// \brief Get the next available RAN-UE-ID. + ran_ue_id_t get_next_ran_ue_id() + { + // return invalid when no RAN-UE-ID is available + if (ue_index_to_ran_ue_id.size() == MAX_NOF_RAN_UES) { + return ran_ue_id_t::invalid; + } + + // iterate over all ids starting with the next_ran_ue_id to find the available id + while (true) { + // Only iterate over ue_index_to_ran_ue_id (size=MAX_NOF_UES_PER_DU) + // to avoid iterating over all possible values of ran_ue_id_t (size=2^32-1) + auto it = std::find_if(ue_index_to_ran_ue_id.begin(), ue_index_to_ran_ue_id.end(), [this](auto& u) { + return u.second == next_ran_ue_id; + }); + + // return the id if it is not already used + if (it == ue_index_to_ran_ue_id.end()) { + ran_ue_id_t ret = next_ran_ue_id; + // increase the next cu ue f1ap id + increase_next_ran_ue_id(); + return ret; + } + + // increase the next cu ue f1ap id and try again + increase_next_ran_ue_id(); + } + + return ran_ue_id_t::invalid; + } + +protected: + ran_ue_id_t next_ran_ue_id = ran_ue_id_t::min; + +private: + srslog::basic_logger& logger; + + inline void increase_next_ran_ue_id() + { + if (next_ran_ue_id == ran_ue_id_t::max) { + // reset ran ue id counter + next_ran_ue_id = ran_ue_id_t::min; + } else { + // increase ran ue id counter + next_ran_ue_id = uint_to_ran_ue_id(ran_ue_id_to_uint(next_ran_ue_id) + 1); + } + } + + // Note: Given that UEs will self-remove from the map, we don't want to destructor to clear the lookups beforehand. + std::unordered_map ue_index_to_ran_ue_id; // indexed by ue_index + std::unordered_map amf_ue_id_to_ran_ue_id; // indexed by amf_ue_id_t + std::unordered_map ues; // indexed by ran_ue_id_t +}; + +} // namespace srs_cu_cp +} // namespace srsran \ No newline at end of file diff --git a/lib/ofh/CMakeLists.txt b/lib/ofh/CMakeLists.txt index 57a063d757..6b22f38ba8 100644 --- a/lib/ofh/CMakeLists.txt +++ b/lib/ofh/CMakeLists.txt @@ -27,7 +27,7 @@ add_subdirectory(timing) add_subdirectory(transmitter) add_library(srsran_ofh ofh_factories.cpp ofh_sector_impl.cpp ofh_sector_controller.cpp) -target_link_libraries(srsran_ofh srsran_ofh_receiver srsran_ofh_timing srsran_ofh_transmitter srsran_ofh_message_serdes srsran_ofh_compression srsran_ofh_ethernet srsran_ofh_ecpri) +target_link_libraries(srsran_ofh srsran_ofh_receiver srsran_ofh_timing srsran_ofh_transmitter srsran_ofh_message_serdes) if (DPDK_FOUND) add_definitions(-DDPDK_FOUND) diff --git a/lib/ofh/ecpri/ecpri_packet_decoder_impl.cpp b/lib/ofh/ecpri/ecpri_packet_decoder_impl.cpp index 9fa1723173..797b5e191d 100644 --- a/lib/ofh/ecpri/ecpri_packet_decoder_impl.cpp +++ b/lib/ofh/ecpri/ecpri_packet_decoder_impl.cpp @@ -43,14 +43,14 @@ static void deserialize_header(ofh::network_order_binary_deserializer& deseriali static bool is_header_valid(const common_header& header, srslog::basic_logger& logger) { if (header.revision != ECPRI_PROTOCOL_REVISION) { - logger.info("Dropping incoming eCPRI packet as the detected eCPRI protocol revision '{}' is not supported", - header.revision); + logger.debug("Dropping incoming eCPRI packet as the detected eCPRI protocol revision '{}' is not supported", + header.revision); return false; } if (!header.is_last_packet) { - logger.info("Dropping incoming eCPRI packet as the current implementation does not support concatenation"); + logger.debug("Dropping incoming eCPRI packet as the current implementation does not support concatenation"); return false; } @@ -94,10 +94,10 @@ span packet_decoder_impl::decode_header(span { // Sanity size check. if (units::bytes(packet.size()) < ECPRI_COMMON_HEADER_SIZE) { - logger.info("Dropping incoming eCPRI packet as its size is {} bytes which is smaller than the eCPRI common header " - "size ({})", - packet.size(), - ECPRI_COMMON_HEADER_SIZE); + logger.debug("Dropping incoming eCPRI packet as its size is {} bytes which is smaller than the eCPRI common header " + "size ({})", + packet.size(), + ECPRI_COMMON_HEADER_SIZE); return {}; } @@ -116,10 +116,10 @@ span packet_decoder_use_header_payload_size::decode_payload(span< packet_parameters& params) { if (params.header.payload_size > units::bytes(packet.size())) { - logger.info("Dropping incoming eCPRI packet as its size is {} bytes while the payload size field in the header is " - "set to {} bytes", - packet.size(), - params.header.payload_size); + logger.debug("Dropping incoming eCPRI packet as its size is {} bytes while the payload size field in the header is " + "set to {} bytes", + packet.size(), + params.header.payload_size); return {}; } @@ -136,8 +136,8 @@ span packet_decoder_use_header_payload_size::decode_payload(span< return packet.subspan(deserializer.get_offset(), (params.header.payload_size - ECPRI_REALTIME_CONTROL_PACKET_FIELDS_SIZE).value()); default: - logger.info("Dropping incoming eCPRI packet as type value '{}' is not supported", - static_cast(params.header.msg_type)); + logger.debug("Dropping incoming eCPRI packet as type value '{}' is not supported", + static_cast(params.header.msg_type)); break; } @@ -157,8 +157,8 @@ span packet_decoder_ignore_header_payload_size::decode_payload(sp params.type_params = deserialize_rt_control_parameters(deserializer); return packet.subspan(deserializer.get_offset(), deserializer.remaining_bytes()); default: - logger.info("Dropping incoming eCPRI packet as type value '{}' is not supported", - static_cast(params.header.msg_type)); + logger.debug("Dropping incoming eCPRI packet as type value '{}' is not supported", + static_cast(params.header.msg_type)); break; } diff --git a/lib/ofh/ofh_factories.cpp b/lib/ofh/ofh_factories.cpp index 229644ddf1..338f9413bf 100644 --- a/lib/ofh/ofh_factories.cpp +++ b/lib/ofh/ofh_factories.cpp @@ -22,13 +22,7 @@ #include "srsran/ofh/ofh_factories.h" #include "ofh_sector_impl.h" -#include "receiver/ofh_receiver_impl.h" -#include "serdes/ofh_cplane_message_builder_dynamic_compression_impl.h" -#include "serdes/ofh_cplane_message_builder_static_compression_impl.h" -#include "serdes/ofh_uplane_message_builder_dynamic_compression_impl.h" -#include "serdes/ofh_uplane_message_builder_static_compression_impl.h" -#include "serdes/ofh_uplane_message_decoder_dynamic_compression_impl.h" -#include "serdes/ofh_uplane_message_decoder_static_compression_impl.h" +#include "receiver/ofh_receiver_factories.h" #include "support/uplink_context_repository.h" #include "support/uplink_cplane_context_repository.h" #include "timing/ofh_ota_symbol_dispatcher.h" @@ -46,6 +40,7 @@ #include "srsran/ofh/ecpri/ecpri_factories.h" #include "srsran/ofh/ethernet/ethernet_factories.h" #include "srsran/ofh/ethernet/ethernet_properties.h" +#include "srsran/ofh/serdes/ofh_serdes_factories.h" #ifdef DPDK_FOUND #include "ethernet/dpdk/dpdk_ethernet_factories.h" @@ -121,54 +116,6 @@ create_data_flow_uplane_data(const transmitter_config& tx_config, return std::make_unique(std::move(config)); } -std::unique_ptr srsran::ofh::create_ofh_control_plane_static_compression_message_builder() -{ - return std::make_unique(); -} - -std::unique_ptr srsran::ofh::create_ofh_control_plane_dynamic_compression_message_builder() -{ - return std::make_unique(); -} - -std::unique_ptr -srsran::ofh::create_static_compr_method_ofh_user_plane_packet_builder(srslog::basic_logger& logger, - iq_compressor& compressor) -{ - return std::make_unique(logger, compressor); -} - -std::unique_ptr -srsran::ofh::create_dynamic_compr_method_ofh_user_plane_packet_builder(srslog::basic_logger& logger, - iq_compressor& compressor) -{ - return std::make_unique(logger, compressor); -} - -std::unique_ptr -srsran::ofh::create_static_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, - subcarrier_spacing scs, - cyclic_prefix cp, - unsigned ru_nof_prbs, - iq_decompressor& decompressor, - const ru_compression_params& compr_params, - const ru_compression_params& prach_compr_params) -{ - return std::make_unique( - logger, scs, get_nsymb_per_slot(cp), ru_nof_prbs, decompressor, compr_params, prach_compr_params); -} - -std::unique_ptr -srsran::ofh::create_dynamic_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, - subcarrier_spacing scs, - cyclic_prefix cp, - unsigned ru_nof_prbs, - iq_decompressor& decompressor) -{ - return std::make_unique( - logger, scs, get_nsymb_per_slot(cp), ru_nof_prbs, decompressor); -} - std::unique_ptr srsran::ofh::create_ofh_timing_controller(const controller_config& config) { realtime_worker_cfg rt_cfg = {config.cp, config.scs, config.gps_Alpha, config.gps_Beta}; @@ -189,18 +136,17 @@ srsran::ofh::create_ofh_ota_symbol_notifier(unsigned nof static receiver_config generate_receiver_config(const sector_configuration& config) { receiver_config rx_config; - - rx_config.scs = config.scs; - rx_config.ru_nof_prbs = - get_max_Nprb(bs_channel_bandwidth_to_MHz(config.ru_operating_bw), config.scs, frequency_range::FR1); - rx_config.is_prach_cp_enabled = config.is_prach_control_plane_enabled; - span prach_eaxc = span(config.prach_eaxc) - .first(std::min(config.prach_eaxc.size(), config.nof_antennas_ul)); - rx_config.prach_eaxc.assign(prach_eaxc.begin(), prach_eaxc.end()); - span ul_eaxc = span(config.ul_eaxc).first(config.nof_antennas_ul); - rx_config.ul_eaxc.assign(ul_eaxc.begin(), ul_eaxc.end()); - rx_config.is_uplink_static_compr_hdr_enabled = config.is_uplink_static_compr_hdr_enabled; + rx_config.ru_operating_bw = config.ru_operating_bw; + rx_config.scs = config.scs; rx_config.cp = config.cp; + rx_config.prach_eaxc = config.prach_eaxc; + rx_config.ul_eaxc = config.ul_eaxc; + rx_config.is_uplink_static_compr_hdr_enabled = config.is_uplink_static_compr_hdr_enabled; + rx_config.prach_compression_params = config.prach_compression_params; + rx_config.ul_compression_params = config.ul_compression_params; + rx_config.is_prach_control_plane_enabled = config.is_prach_control_plane_enabled; + rx_config.ignore_ecpri_payload_size_field = config.ignore_ecpri_payload_size_field; + // In rx, dst and src addresses are swapped. rx_config.mac_dst_address = config.mac_src_address; rx_config.mac_src_address = config.mac_dst_address; @@ -210,51 +156,6 @@ static receiver_config generate_receiver_config(const sector_configuration& conf return rx_config; } -static receiver_impl_dependencies -resolve_receiver_dependencies(const sector_configuration& sector_cfg, - const receiver_config& rx_config, - std::shared_ptr> prach_context_repo, - std::shared_ptr> ul_slot_context_repo, - std::shared_ptr ul_cp_context_repo, - uplane_rx_symbol_notifier& notifier) -{ - receiver_impl_dependencies dependencies; - - dependencies.logger = sector_cfg.logger; - dependencies.notifier = ¬ifier; - - std::array, ofh::NOF_COMPRESSION_TYPES_SUPPORTED> decompressors; - for (unsigned i = 0; i != ofh::NOF_COMPRESSION_TYPES_SUPPORTED; ++i) { - decompressors[i] = create_iq_decompressor(static_cast(i), *dependencies.logger); - } - dependencies.decompressor_sel = create_iq_decompressor_selector(std::move(decompressors)); - - dependencies.uplane_decoder = - (rx_config.is_uplink_static_compr_hdr_enabled) - ? ofh::create_static_compr_method_ofh_user_plane_packet_decoder(*sector_cfg.logger, - rx_config.scs, - rx_config.cp, - rx_config.ru_nof_prbs, - *dependencies.decompressor_sel, - sector_cfg.ul_compression_params, - sector_cfg.prach_compression_params) - : ofh::create_dynamic_compr_method_ofh_user_plane_packet_decoder( - *sector_cfg.logger, rx_config.scs, rx_config.cp, rx_config.ru_nof_prbs, *dependencies.decompressor_sel); - - if (sector_cfg.ignore_ecpri_payload_size_field) { - dependencies.ecpri_decoder = ecpri::create_ecpri_packet_decoder_ignoring_payload_size(*sector_cfg.logger); - } else { - dependencies.ecpri_decoder = ecpri::create_ecpri_packet_decoder_using_payload_size(*sector_cfg.logger); - } - dependencies.eth_frame_decoder = ether::create_vlan_frame_decoder(*sector_cfg.logger); - - dependencies.prach_context_repo = prach_context_repo; - dependencies.ul_slot_context_repo = ul_slot_context_repo; - dependencies.ul_cp_context_repo = ul_cp_context_repo; - - return dependencies; -} - static transmitter_config generate_transmitter_config(const sector_configuration& sector_cfg) { transmitter_config tx_config; @@ -318,12 +219,28 @@ create_downlink_handler(const transmitter_config& tx_con auto data_flow_uplane = std::make_unique(std::move(df_uplane_task_dispatcher_cfg)); + unsigned nof_symbols = get_nsymb_per_slot(tx_config.cp); + unsigned dl_processing_time_in_symbols = + std::floor(tx_config.dl_processing_time / std::chrono::duration( + 1e6 / (nof_symbols * get_nof_slots_per_subframe(tx_config.scs)))); + + unsigned nof_symbols_before_ota = + dl_processing_time_in_symbols + get_biggest_min_tx_parameter_in_symbols( + tx_config.symbol_handler_cfg.tx_timing_params, nof_symbols, tx_config.scs); + if (tx_config.downlink_broadcast) { - return std::make_unique(tx_config.cp, + auto window_checker = std::make_unique( + logger, nof_symbols_before_ota, nof_symbols, to_numerology_value(tx_config.scs)); + + window_handler = window_checker.get(); + + return std::make_unique(logger, + tx_config.cp, tx_config.tdd_config, tx_config.dl_eaxc, std::move(data_flow_cplane), - std::move(data_flow_uplane)); + std::move(data_flow_uplane), + std::move(window_checker)); } downlink_handler_impl_config dl_config; @@ -331,15 +248,6 @@ create_downlink_handler(const transmitter_config& tx_con dl_config.tdd_config = tx_config.tdd_config; dl_config.cp = tx_config.cp; - unsigned nof_symbols = get_nsymb_per_slot(tx_config.cp); - unsigned dl_processing_time_in_symbols = - std::floor(tx_config.dl_processing_time / std::chrono::duration( - 1e6 / (nof_symbols * get_nof_slots_per_subframe(tx_config.scs)))); - - unsigned nof_symbols_before_ota = - dl_processing_time_in_symbols + get_biggest_min_tx_parameter_in_symbols( - tx_config.symbol_handler_cfg.tx_timing_params, nof_symbols, tx_config.scs); - downlink_handler_impl_dependencies dl_dependencies; dl_dependencies.logger = &logger; dl_dependencies.data_flow_cplane = std::move(data_flow_cplane); @@ -354,12 +262,12 @@ create_downlink_handler(const transmitter_config& tx_con } static std::unique_ptr -create_uplink_request_handler(const transmitter_config& tx_config, - srslog::basic_logger& logger, - std::shared_ptr frame_pool, - std::shared_ptr> prach_context_repo, - std::shared_ptr> ul_slot_context_repo, - std::shared_ptr ul_cp_context_repo) +create_uplink_request_handler(const transmitter_config& tx_config, + srslog::basic_logger& logger, + std::shared_ptr frame_pool, + std::shared_ptr prach_context_repo, + std::shared_ptr ul_slot_context_repo, + std::shared_ptr ul_cp_context_repo) { uplink_request_handler_impl_config config; config.is_prach_cp_enabled = tx_config.is_prach_cp_enabled; @@ -377,11 +285,11 @@ create_uplink_request_handler(const transmitter_config& } static transmitter_impl_dependencies -resolve_transmitter_dependencies(const sector_configuration& sector_cfg, - const transmitter_config& tx_config, - std::shared_ptr> prach_context_repo, - std::shared_ptr> ul_slot_context_repo, - std::shared_ptr ul_cp_context_repo) +resolve_transmitter_dependencies(const sector_configuration& sector_cfg, + const transmitter_config& tx_config, + std::shared_ptr prach_context_repo, + std::shared_ptr ul_slot_context_repo, + std::shared_ptr ul_cp_context_repo) { transmitter_impl_dependencies dependencies; @@ -424,14 +332,12 @@ std::unique_ptr srsran::ofh::create_ofh_sector(const sector_configuratio unsigned repository_size = sector_cfg.max_processing_delay_slots * 4; auto cp_repo = std::make_shared(repository_size); - auto prach_repo = std::make_shared>(repository_size); - auto slot_repo = std::make_shared>(repository_size); + auto prach_repo = std::make_shared(repository_size); + auto slot_repo = std::make_shared(repository_size); // Build the OFH receiver. auto rx_config = generate_receiver_config(sector_cfg); - auto receiver = std::make_unique( - rx_config, - resolve_receiver_dependencies(sector_cfg, rx_config, prach_repo, slot_repo, cp_repo, *sector_cfg.notifier)); + auto receiver = create_receiver(rx_config, *sector_cfg.logger, *sector_cfg.notifier, prach_repo, slot_repo, cp_repo); // Build the OFH transmitter. auto tx_config = generate_transmitter_config(sector_cfg); diff --git a/lib/ofh/ofh_sector_impl.h b/lib/ofh/ofh_sector_impl.h index aae185130b..8b65f45bfd 100644 --- a/lib/ofh/ofh_sector_impl.h +++ b/lib/ofh/ofh_sector_impl.h @@ -23,11 +23,12 @@ #pragma once #include "ofh_sector_controller.h" +#include "support/prach_context_repository.h" #include "support/uplink_context_repository.h" #include "support/uplink_cplane_context_repository.h" #include "srsran/ofh/ethernet/ethernet_receiver.h" -#include "srsran/ofh/ofh_receiver.h" #include "srsran/ofh/ofh_sector.h" +#include "srsran/ofh/receiver/ofh_receiver.h" #include "srsran/ofh/transmitter/ofh_transmitter.h" namespace srsran { @@ -37,12 +38,12 @@ namespace ofh { class sector_impl : public sector { public: - sector_impl(std::unique_ptr receiver_, - std::unique_ptr transmitter_, - std::shared_ptr cp_repo_, - std::shared_ptr> prach_repo_, - std::shared_ptr> slot_repo_, - std::unique_ptr eth_receiver_) : + sector_impl(std::unique_ptr receiver_, + std::unique_ptr transmitter_, + std::shared_ptr cp_repo_, + std::shared_ptr prach_repo_, + std::shared_ptr slot_repo_, + std::unique_ptr eth_receiver_) : eth_receiver(std::move(eth_receiver_)), cp_repo(std::move(cp_repo_)), prach_repo(std::move(prach_repo_)), @@ -69,13 +70,13 @@ class sector_impl : public sector controller& get_controller() override; private: - std::unique_ptr eth_receiver; - std::shared_ptr cp_repo; - std::shared_ptr> prach_repo; - std::shared_ptr> slot_repo; - std::unique_ptr ofh_receiver; - std::unique_ptr ofh_transmitter; - sector_controller ofh_sector_controller; + std::unique_ptr eth_receiver; + std::shared_ptr cp_repo; + std::shared_ptr prach_repo; + std::shared_ptr slot_repo; + std::unique_ptr ofh_receiver; + std::unique_ptr ofh_transmitter; + sector_controller ofh_sector_controller; }; } // namespace ofh diff --git a/lib/ofh/receiver/CMakeLists.txt b/lib/ofh/receiver/CMakeLists.txt index 8960257a8c..29a66dca68 100644 --- a/lib/ofh/receiver/CMakeLists.txt +++ b/lib/ofh/receiver/CMakeLists.txt @@ -22,14 +22,13 @@ set(SOURCES ofh_data_flow_uplane_uplink_data_impl.cpp ofh_data_flow_uplane_uplink_prach_impl.cpp ofh_message_receiver.cpp + ofh_receiver_factories.cpp ofh_receiver_impl.cpp ofh_rx_window_checker.cpp ofh_uplane_rx_symbol_data_flow_notifier.cpp ofh_uplane_rx_symbol_data_flow_writer.cpp ofh_uplane_prach_data_flow_notifier.cpp - ofh_uplane_prach_symbol_data_flow_writer.cpp - ofh_uplane_uplink_packet_handler.cpp - ofh_uplane_uplink_symbol_manager.cpp) + ofh_uplane_prach_symbol_data_flow_writer.cpp) add_library(srsran_ofh_receiver STATIC ${SOURCES}) -target_link_libraries(srsran_ofh_receiver srsran_ran srsran_ofh_message_serdes srslog) +target_link_libraries(srsran_ofh_receiver srsran_ran srsran_ofh_message_serdes srsran_ofh_compression srsran_ofh_ethernet srsran_ofh_ecpri srslog) diff --git a/lib/ofh/receiver/ofh_data_flow_uplane_uplink_data_impl.h b/lib/ofh/receiver/ofh_data_flow_uplane_uplink_data_impl.h index 947736c176..fbe8394a65 100644 --- a/lib/ofh/receiver/ofh_data_flow_uplane_uplink_data_impl.h +++ b/lib/ofh/receiver/ofh_data_flow_uplane_uplink_data_impl.h @@ -22,7 +22,7 @@ #pragma once -#include "../support/uplink_context_repo.h" +#include "../support/uplink_context_repository.h" #include "../support/uplink_cplane_context_repository.h" #include "ofh_data_flow_uplane_uplink_data.h" #include "ofh_uplane_rx_symbol_data_flow_notifier.h" @@ -49,7 +49,7 @@ struct data_flow_uplane_uplink_data_impl_dependencies { /// Control-Plane context repository. std::shared_ptr ul_cplane_context_repo_ptr; /// Uplink context repository. - std::shared_ptr ul_context_repo; + std::shared_ptr ul_context_repo; /// User-Plane message decoder. std::unique_ptr uplane_decoder; }; diff --git a/lib/ofh/receiver/ofh_message_receiver.cpp b/lib/ofh/receiver/ofh_message_receiver.cpp index a1a2a13ae7..a14d5ca496 100644 --- a/lib/ofh/receiver/ofh_message_receiver.cpp +++ b/lib/ofh/receiver/ofh_message_receiver.cpp @@ -30,7 +30,7 @@ message_receiver::message_receiver(const message_receiver_config& config, message_receiver_dependencies&& dependencies) : logger(*dependencies.logger), vlan_params(config.vlan_params), - ul_prach_eaxc(config.ul_prach_eaxc), + ul_prach_eaxc(config.prach_eaxc), ul_eaxc(config.ul_eaxc), window_checker(*dependencies.window_checker), vlan_decoder(std::move(dependencies.eth_frame_decoder)), @@ -60,6 +60,19 @@ void message_receiver::on_new_frame(span payload) return; } + // Verify the sequence identifier. + const ecpri::iq_data_parameters& ecpri_iq_params = variant_get(ecpri_params.type_params); + int nof_skipped_seq_id = + seq_id_checker.update_and_compare_seq_id(ecpri_iq_params.pc_id, (ecpri_iq_params.seq_id >> 8)); + // Drop the message when it is from the past. + if (nof_skipped_seq_id < 0) { + logger.debug("Dropping Open Fronthaul User-Plane message as sequence identifier was from the past"); + + return; + } else if (nof_skipped_seq_id > 0) { + logger.warning("Detected {} lost messages", nof_skipped_seq_id); + } + slot_symbol_point slot_point = uplane_decoder->peek_slot_symbol_point(ofh_pdu); if (!slot_point.get_slot().valid()) { return; @@ -81,7 +94,7 @@ void message_receiver::on_new_frame(span payload) bool message_receiver::should_ecpri_packet_be_filtered(const ecpri::packet_parameters& ecpri_params) const { if (ecpri_params.header.msg_type != ecpri::message_type::iq_data) { - logger.debug("Dropping Open Fronthaul User-Plane packet as decoded eCPRI message type is not IQ data"); + logger.debug("Dropping Open Fronthaul User-Plane message as decoded eCPRI message type is not IQ data"); return true; } @@ -89,20 +102,18 @@ bool message_receiver::should_ecpri_packet_be_filtered(const ecpri::packet_param const ecpri::iq_data_parameters& ecpri_iq_params = variant_get(ecpri_params.type_params); if ((std::find(ul_eaxc.begin(), ul_eaxc.end(), ecpri_iq_params.pc_id) == ul_eaxc.end()) && (std::find(ul_prach_eaxc.begin(), ul_prach_eaxc.end(), ecpri_iq_params.pc_id) == ul_prach_eaxc.end())) { - logger.debug("Dropping Open Fronthaul User-Plane packet as decoded eAxC is {}", ecpri_iq_params.pc_id); + logger.debug("Dropping Open Fronthaul User-Plane message as decoded eAxC is {}", ecpri_iq_params.pc_id); return true; } - // :TODO: check sequence id. - return false; } bool message_receiver::should_ethernet_frame_be_filtered(const ether::vlan_frame_params& eth_params) const { if (eth_params.mac_src_address != vlan_params.mac_src_address) { - logger.debug("Dropping Open Fronthaul User-Plane packet as source MAC addresses doesn't match(detected={:x}, " + logger.debug("Dropping Ethernet packet as source MAC addresses doesn't match(detected={:x}, " "expected={:x})", span(eth_params.mac_src_address), span(vlan_params.mac_src_address)); @@ -111,7 +122,7 @@ bool message_receiver::should_ethernet_frame_be_filtered(const ether::vlan_frame } if (eth_params.mac_dst_address != vlan_params.mac_dst_address) { - logger.debug("Dropping Open Fronthaul User-Plane packet as destination MAC addresses doesn't match(detected={:x}, " + logger.debug("Dropping Ethernet packet as destination MAC addresses doesn't match(detected={:x}, " "expected={:x})", span(eth_params.mac_dst_address), span(vlan_params.mac_dst_address)); @@ -120,7 +131,7 @@ bool message_receiver::should_ethernet_frame_be_filtered(const ether::vlan_frame } if (eth_params.eth_type != vlan_params.eth_type) { - logger.debug("Dropping Open Fronthaul User-Plane packet as decoded Ethernet type is {} and it is expected {}", + logger.debug("Dropping Ethernet packet as decoded Ethernet type is {} and it is expected {}", eth_params.eth_type, vlan_params.eth_type); diff --git a/lib/ofh/receiver/ofh_message_receiver.h b/lib/ofh/receiver/ofh_message_receiver.h index 4000988123..cccb1b7c49 100644 --- a/lib/ofh/receiver/ofh_message_receiver.h +++ b/lib/ofh/receiver/ofh_message_receiver.h @@ -24,6 +24,7 @@ #include "ofh_data_flow_uplane_uplink_data.h" #include "ofh_data_flow_uplane_uplink_prach.h" +#include "ofh_sequence_id_checker.h" #include "srsran/adt/static_vector.h" #include "srsran/ofh/ecpri/ecpri_packet_decoder.h" #include "srsran/ofh/ethernet/ethernet_frame_notifier.h" @@ -43,7 +44,7 @@ struct message_receiver_config { /// VLAN ethernet frame parameters. ether::vlan_frame_params vlan_params; /// Uplink PRACH eAxC. - static_vector ul_prach_eaxc; + static_vector prach_eaxc; /// Uplink eAxC. static_vector ul_eaxc; }; @@ -90,6 +91,7 @@ class message_receiver : public ether::frame_notifier const ether::vlan_frame_params vlan_params; const static_vector ul_prach_eaxc; const static_vector ul_eaxc; + sequence_id_checker seq_id_checker; rx_window_checker& window_checker; std::unique_ptr vlan_decoder; std::unique_ptr ecpri_decoder; diff --git a/lib/ofh/receiver/ofh_receiver_factories.cpp b/lib/ofh/receiver/ofh_receiver_factories.cpp new file mode 100644 index 0000000000..520e3b679d --- /dev/null +++ b/lib/ofh/receiver/ofh_receiver_factories.cpp @@ -0,0 +1,146 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "ofh_receiver_factories.h" +#include "ofh_data_flow_uplane_uplink_data_impl.h" +#include "ofh_data_flow_uplane_uplink_prach_impl.h" +#include "ofh_receiver_impl.h" +#include "srsran/ofh/compression/compression_factory.h" +#include "srsran/ofh/ecpri/ecpri_factories.h" +#include "srsran/ofh/ethernet/ethernet_factories.h" +#include "srsran/ofh/serdes/ofh_serdes_factories.h" + +using namespace srsran; +using namespace ofh; + +static std::unique_ptr create_uplane_decoder(const receiver_config& receiver_cfg, + srslog::basic_logger& logger) +{ + // Comrpessors. + std::array, ofh::NOF_COMPRESSION_TYPES_SUPPORTED> decompr; + for (unsigned i = 0; i != ofh::NOF_COMPRESSION_TYPES_SUPPORTED; ++i) { + decompr[i] = create_iq_decompressor(static_cast(i), logger); + } + + unsigned nof_prbs_ru = + get_max_Nprb(bs_channel_bandwidth_to_MHz(receiver_cfg.ru_operating_bw), receiver_cfg.scs, frequency_range::FR1); + + // Open FrontHaul decoder. + return (receiver_cfg.is_uplink_static_compr_hdr_enabled) + ? ofh::create_static_compr_method_ofh_user_plane_packet_decoder( + logger, + receiver_cfg.scs, + receiver_cfg.cp, + nof_prbs_ru, + create_iq_decompressor_selector(std::move(decompr)), + receiver_cfg.ul_compression_params, + receiver_cfg.prach_compression_params) + : ofh::create_dynamic_compr_method_ofh_user_plane_packet_decoder( + logger, + receiver_cfg.scs, + receiver_cfg.cp, + nof_prbs_ru, + create_iq_decompressor_selector(std::move(decompr))); +} + +static std::unique_ptr +create_uplink_prach_data_flow(const receiver_config& receiver_cfg, + srslog::basic_logger& logger, + uplane_rx_symbol_notifier& notifier, + std::shared_ptr prach_context_repo, + std::shared_ptr ul_cp_context_repo) +{ + data_flow_uplane_uplink_prach_impl_config config; + config.is_prach_cplane_enabled = receiver_cfg.is_prach_control_plane_enabled; + config.prach_eaxcs = receiver_cfg.prach_eaxc; + + data_flow_uplane_uplink_prach_impl_dependencies dependencies; + dependencies.logger = &logger; + dependencies.notifier = ¬ifier; + dependencies.ul_cplane_context_repo_ptr = ul_cp_context_repo; + dependencies.prach_context_repo = prach_context_repo; + dependencies.uplane_decoder = create_uplane_decoder(receiver_cfg, logger); + + return std::make_unique(config, std::move(dependencies)); +} + +static std::unique_ptr +create_uplink_data_flow(const receiver_config& receiver_cfg, + srslog::basic_logger& logger, + uplane_rx_symbol_notifier& notifier, + std::shared_ptr ul_slot_context_repo, + std::shared_ptr ul_cp_context_repo) +{ + data_flow_uplane_uplink_data_impl_config config; + config.ul_eaxc = receiver_cfg.ul_eaxc; + + data_flow_uplane_uplink_data_impl_dependencies dependencies; + dependencies.logger = &logger; + dependencies.notifier = ¬ifier; + dependencies.ul_cplane_context_repo_ptr = ul_cp_context_repo; + dependencies.ul_context_repo = ul_slot_context_repo; + dependencies.uplane_decoder = create_uplane_decoder(receiver_cfg, logger); + + return std::make_unique(config, std::move(dependencies)); +} + +static receiver_impl_dependencies +resolve_receiver_dependencies(const receiver_config& receiver_cfg, + srslog::basic_logger& logger, + uplane_rx_symbol_notifier& notifier, + std::shared_ptr prach_context_repo, + std::shared_ptr ul_slot_context_repo, + std::shared_ptr ul_cp_context_repo) +{ + receiver_impl_dependencies dependencies; + + dependencies.logger = &logger; + dependencies.uplane_decoder = create_uplane_decoder(receiver_cfg, logger); + + if (receiver_cfg.ignore_ecpri_payload_size_field) { + dependencies.ecpri_decoder = ecpri::create_ecpri_packet_decoder_ignoring_payload_size(logger); + } else { + dependencies.ecpri_decoder = ecpri::create_ecpri_packet_decoder_using_payload_size(logger); + } + dependencies.eth_frame_decoder = ether::create_vlan_frame_decoder(logger); + + dependencies.data_flow_uplink = + create_uplink_data_flow(receiver_cfg, logger, notifier, ul_slot_context_repo, ul_cp_context_repo); + dependencies.data_flow_prach = + create_uplink_prach_data_flow(receiver_cfg, logger, notifier, prach_context_repo, ul_cp_context_repo); + + return dependencies; +} + +std::unique_ptr +srsran::ofh::create_receiver(const receiver_config& receiver_cfg, + srslog::basic_logger& logger, + uplane_rx_symbol_notifier& notifier, + std::shared_ptr prach_context_repo, + std::shared_ptr ul_slot_context_repo, + std::shared_ptr ul_cp_context_repo) +{ + auto rx_depen = resolve_receiver_dependencies( + receiver_cfg, logger, notifier, prach_context_repo, ul_slot_context_repo, ul_cp_context_repo); + + return std::make_unique(receiver_cfg, std::move(rx_depen)); +} diff --git a/lib/ofh/receiver/ofh_receiver_factories.h b/lib/ofh/receiver/ofh_receiver_factories.h new file mode 100644 index 0000000000..e0104ccfdc --- /dev/null +++ b/lib/ofh/receiver/ofh_receiver_factories.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "../support/prach_context_repository.h" +#include "../support/uplink_context_repository.h" +#include "../support/uplink_cplane_context_repository.h" +#include "srsran/ofh/receiver/ofh_receiver.h" +#include "srsran/ofh/receiver/ofh_receiver_configuration.h" +#include + +namespace srsran { +namespace ofh { + +/// Creates a receiver with the given configuration and dependencies. +std::unique_ptr create_receiver(const receiver_config& receiver_cfg, + srslog::basic_logger& logger, + uplane_rx_symbol_notifier& notifier, + std::shared_ptr prach_context_repo, + std::shared_ptr ul_slot_context_repo, + std::shared_ptr ul_cp_context_repo); + +} // namespace ofh +} // namespace srsran diff --git a/lib/ofh/receiver/ofh_receiver_impl.cpp b/lib/ofh/receiver/ofh_receiver_impl.cpp index 7947d59b33..91880c4821 100644 --- a/lib/ofh/receiver/ofh_receiver_impl.cpp +++ b/lib/ofh/receiver/ofh_receiver_impl.cpp @@ -26,73 +26,55 @@ using namespace srsran; using namespace ofh; -/// Returns an Open Fronthaul uplink packet handler configuration from the given receiver implementation configuration. -static uplane_uplink_packet_handler_config get_packet_handler_config(const receiver_config& config, - receiver_impl_dependencies& dependencies) +static message_receiver_config get_message_receiver_configuration(const receiver_config& rx_config) { - uplane_uplink_packet_handler_config out_cfg(*dependencies.logger); - out_cfg.is_prach_cp_enabled = config.is_prach_cp_enabled; - out_cfg.ul_prach_eaxc = config.prach_eaxc; - out_cfg.ul_eaxc = config.ul_eaxc; + message_receiver_config config; - srsran_assert(dependencies.uplane_decoder, "Invalid user plane decoder"); - out_cfg.uplane_decoder = std::move(dependencies.uplane_decoder); - srsran_assert(dependencies.ecpri_decoder, "Invalid eCPRI decoder"); - out_cfg.ecpri_decoder = std::move(dependencies.ecpri_decoder); - srsran_assert(dependencies.eth_frame_decoder, "Invalid ethernet frame decoder"); - out_cfg.eth_frame_decoder = std::move(dependencies.eth_frame_decoder); - srsran_assert(dependencies.ul_cp_context_repo, "Invalid control plane repository"); - out_cfg.cplane_repo = dependencies.ul_cp_context_repo; + config.vlan_params.mac_src_address = rx_config.mac_src_address; + config.vlan_params.mac_dst_address = rx_config.mac_dst_address; + config.vlan_params.tci = rx_config.tci; + config.vlan_params.eth_type = ether::ECPRI_ETH_TYPE; - // VLAN configuration. - out_cfg.vlan_params.eth_type = ether::ECPRI_ETH_TYPE; - out_cfg.vlan_params.tci = config.tci; - out_cfg.vlan_params.mac_dst_address = config.mac_dst_address; - out_cfg.vlan_params.mac_src_address = config.mac_src_address; + config.prach_eaxc = rx_config.prach_eaxc; + config.ul_eaxc = rx_config.ul_eaxc; - return out_cfg; + return config; } -/// Returns an Open Fronthaul User-Plane uplink symbol manager configuration from the given receiver implementation -/// configuration and handlers. -static uplane_uplink_symbol_manager_config -get_uplink_symbol_manager_config(receiver_impl_dependencies& dependencies, - uplane_uplink_packet_handler& packet_handler, - const static_vector& ru_ul_eaxc, - const static_vector& ru_prach_eaxc, - rx_window_checker& rx_window) +static message_receiver_dependencies get_message_receiver_dependencies(receiver_impl_dependencies&& rx_dependencies, + rx_window_checker& window_checker) { - uplane_uplink_symbol_manager_config out_cfg(*dependencies.logger, - *dependencies.notifier, - packet_handler, - dependencies.prach_context_repo, - dependencies.ul_slot_context_repo, - ru_ul_eaxc, - ru_prach_eaxc, - rx_window); + message_receiver_dependencies dependencies; + + dependencies.logger = rx_dependencies.logger; + dependencies.window_checker = &window_checker; + dependencies.ecpri_decoder = std::move(rx_dependencies.ecpri_decoder); + srsran_assert(dependencies.ecpri_decoder, "Invalid eCPRI decoder"); + dependencies.eth_frame_decoder = std::move(rx_dependencies.eth_frame_decoder); + srsran_assert(dependencies.eth_frame_decoder, "Invalid Ethernet frame decoder"); + dependencies.uplane_decoder = std::move(rx_dependencies.uplane_decoder); + srsran_assert(dependencies.uplane_decoder, "Invalid Open Fronthaul User-Plane decoder"); + dependencies.data_flow_uplink = std::move(rx_dependencies.data_flow_uplink); + srsran_assert(dependencies.data_flow_uplink, "Invalid uplink data flow decoder"); + dependencies.data_flow_prach = std::move(rx_dependencies.data_flow_prach); + srsran_assert(dependencies.data_flow_prach, "Invalid PRACH data flow decoder"); - return out_cfg; + return dependencies; } receiver_impl::receiver_impl(const receiver_config& config, receiver_impl_dependencies&& dependencies) : - decompressor_sel(std::move(dependencies.decompressor_sel)), window_checker(*dependencies.logger, config.rx_timing_params, std::chrono::duration( 1e6 / (get_nsymb_per_slot(config.cp) * get_nof_slots_per_subframe(config.scs)))), - ul_packet_handler(get_packet_handler_config(config, dependencies)), - ul_symbol_manager(get_uplink_symbol_manager_config(dependencies, - ul_packet_handler, - config.ul_eaxc, - config.prach_eaxc, - window_checker)) + msg_receiver(get_message_receiver_configuration(config), + get_message_receiver_dependencies(std::move(dependencies), window_checker)) { - srsran_assert(decompressor_sel, "Invalid decompressor selector"); } ether::frame_notifier& receiver_impl::get_ethernet_frame_notifier() { - return ul_symbol_manager; + return msg_receiver; } ota_symbol_handler& receiver_impl::get_ota_symbol_handler() diff --git a/lib/ofh/receiver/ofh_receiver_impl.h b/lib/ofh/receiver/ofh_receiver_impl.h index 1619a0b2c5..16dfa2b712 100644 --- a/lib/ofh/receiver/ofh_receiver_impl.h +++ b/lib/ofh/receiver/ofh_receiver_impl.h @@ -22,15 +22,13 @@ #pragma once +#include "../support/prach_context_repository.h" #include "../support/uplink_context_repository.h" #include "../support/uplink_cplane_context_repository.h" +#include "ofh_message_receiver.h" #include "ofh_rx_window_checker.h" -#include "ofh_uplane_uplink_packet_handler.h" -#include "ofh_uplane_uplink_symbol_manager.h" -#include "srsran/ofh/ethernet/ethernet_receiver.h" -#include "srsran/ofh/ofh_receiver.h" -#include "srsran/ofh/ofh_receiver_configuration.h" -#include "srsran/support/executors/task_executor.h" +#include "srsran/ofh/receiver/ofh_receiver.h" +#include "srsran/ofh/receiver/ofh_receiver_configuration.h" namespace srsran { namespace ofh { @@ -39,22 +37,16 @@ namespace ofh { struct receiver_impl_dependencies { /// Logger. srslog::basic_logger* logger = nullptr; - /// Open Fronthaul User-Plane received symbol notifier. - uplane_rx_symbol_notifier* notifier = nullptr; - /// PRACH context repository. - std::shared_ptr> prach_context_repo; - /// UL slot context repository. - std::shared_ptr> ul_slot_context_repo; - /// UL Control-Plane context repository. - std::shared_ptr ul_cp_context_repo; - /// Open Fronthaul IQ data decompressor selector. - std::unique_ptr decompressor_sel; - /// Open Fronthaul User-Plane packet decoder. - std::unique_ptr uplane_decoder; /// eCPRI packet decoder. std::unique_ptr ecpri_decoder; /// Ethernet frame decoder. std::unique_ptr eth_frame_decoder; + /// Open Fronthaul User-Plane decoder. + std::unique_ptr uplane_decoder; + /// User-Plane uplink data flow. + std::unique_ptr data_flow_uplink; + /// User-Plane uplink PRACH data flow. + std::unique_ptr data_flow_prach; }; /// \brief Open Fronthaul receiver. @@ -72,10 +64,8 @@ class receiver_impl : public receiver ota_symbol_handler& get_ota_symbol_handler() override; private: - std::unique_ptr decompressor_sel; - rx_window_checker window_checker; - uplane_uplink_packet_handler ul_packet_handler; - uplane_uplink_symbol_manager ul_symbol_manager; + rx_window_checker window_checker; + message_receiver msg_receiver; }; } // namespace ofh diff --git a/lib/ofh/receiver/ofh_rx_window_checker.h b/lib/ofh/receiver/ofh_rx_window_checker.h index a2c818b67a..5b6089ca79 100644 --- a/lib/ofh/receiver/ofh_rx_window_checker.h +++ b/lib/ofh/receiver/ofh_rx_window_checker.h @@ -23,7 +23,7 @@ #pragma once #include "srsran/ofh/ofh_ota_symbol_handler.h" -#include "srsran/ofh/ofh_receiver_configuration.h" +#include "srsran/ofh/receiver/ofh_receiver_timing_parameters.h" #include namespace srsran { diff --git a/lib/ofh/receiver/ofh_sequence_id_checker.h b/lib/ofh/receiver/ofh_sequence_id_checker.h new file mode 100644 index 0000000000..d8bc6d51f8 --- /dev/null +++ b/lib/ofh/receiver/ofh_sequence_id_checker.h @@ -0,0 +1,111 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/bounded_bitset.h" +#include "srsran/adt/circular_map.h" +#include "srsran/ofh/ofh_constants.h" + +namespace srsran { +namespace ofh { + +/// Open Fronthaul sequence identifier checker. +class sequence_id_checker +{ + static constexpr int NOF_SEQUENCES_IDENTIFIERS = 1u << 8; + static constexpr int HALF_NOF_SEQUENCES_IDENTIFIERS = NOF_SEQUENCES_IDENTIFIERS / 2; + + bounded_bitset initialized; + circular_map counters; + +public: + /// Default constructor. + sequence_id_checker() : initialized(MAX_SUPPORTED_EAXC_ID_VALUE) + { + for (unsigned K = 0; K != MAX_SUPPORTED_EAXC_ID_VALUE; ++K) { + counters.insert(K, 0); + } + } + + /// \brief Updates the expected sequence identifier value for the given eAxC and compares it with the given sequence + /// identifier, returning the difference between them. + /// + /// A negative difference means that the sequence identifier is from the past. + /// A difference of 0 means that the current sequence identifier matches the expected. + /// A positive difference means that the sequence identifier belongs to the future. In this case, the expected + /// sequence identifier is updated with the given sequence identifier. + int update_and_compare_seq_id(unsigned eaxc, uint8_t seq_id) + { + srsran_assert(eaxc < MAX_SUPPORTED_EAXC_ID_VALUE, + "Invalid eAxC={} detected. Maximum supported eAxC value = {}", + eaxc, + MAX_SUPPORTED_EAXC_ID_VALUE); + + // First packet is always valid. + if (!initialized.test(eaxc)) { + initialized.set(eaxc); + counters[eaxc] = seq_id; + + return 0; + } + + // Get the expected sequence identifier and update its value. + uint8_t& expected_seq_id = counters[eaxc]; + expected_seq_id++; + + if (seq_id == expected_seq_id) { + return 0; + } + + int nof_skipped_seq_id = get_nof_skipped_sequence_id(seq_id, expected_seq_id); + + // Update the expected sequence identifier when the sequence identifier is from the future. + if (nof_skipped_seq_id > 0) { + expected_seq_id = seq_id; + } + + return nof_skipped_seq_id; + } + +private: + /// \brief Returns the number of skipped sequence identifiers using the given sequence identifier and expected + /// sequence identifier. + /// + /// A negative difference means that the sequence identifier received is from the past. + /// No difference means that the sequence identifier matches the expected. + /// A positive difference means that the sequence identifier is from the future. + static int get_nof_skipped_sequence_id(uint8_t seq_id, uint8_t expected_seq_id) + { + int a = static_cast(seq_id) - static_cast(expected_seq_id); + if (a >= HALF_NOF_SEQUENCES_IDENTIFIERS) { + return a - NOF_SEQUENCES_IDENTIFIERS; + } + if (a < -HALF_NOF_SEQUENCES_IDENTIFIERS) { + return a + NOF_SEQUENCES_IDENTIFIERS; + } + return a; + } +}; + +} // namespace ofh +} // namespace srsran diff --git a/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_notifier.h b/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_notifier.h index 38ba5bfab2..19ad2c1f30 100644 --- a/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_notifier.h +++ b/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_notifier.h @@ -22,7 +22,7 @@ #pragma once -#include "../support/uplink_context_repo.h" +#include "../support/uplink_context_repository.h" namespace srsran { namespace ofh { @@ -33,9 +33,9 @@ class uplane_rx_symbol_notifier; class uplane_rx_symbol_data_flow_notifier { public: - uplane_rx_symbol_data_flow_notifier(srslog::basic_logger& logger_, - std::shared_ptr ul_context_repo_, - uplane_rx_symbol_notifier& notifier_) : + uplane_rx_symbol_data_flow_notifier(srslog::basic_logger& logger_, + std::shared_ptr ul_context_repo_, + uplane_rx_symbol_notifier& notifier_) : logger(logger_), ul_context_repo_ptr(ul_context_repo_), ul_context_repo(*ul_context_repo_ptr), notifier(notifier_) { srsran_assert(ul_context_repo_ptr, "Invalid uplink context repository"); @@ -46,10 +46,10 @@ class uplane_rx_symbol_data_flow_notifier void notify_received_symbol(slot_point slot, unsigned symbol); private: - srslog::basic_logger& logger; - std::shared_ptr ul_context_repo_ptr; - uplink_context_repo& ul_context_repo; - uplane_rx_symbol_notifier& notifier; + srslog::basic_logger& logger; + std::shared_ptr ul_context_repo_ptr; + uplink_context_repository& ul_context_repo; + uplane_rx_symbol_notifier& notifier; }; } // namespace ofh diff --git a/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer.cpp b/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer.cpp index a8617a40a4..a8f4c26e4e 100644 --- a/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer.cpp +++ b/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer.cpp @@ -33,9 +33,9 @@ void uplane_rx_symbol_data_flow_writer::write_to_resource_grid(unsigned unsigned symbol = results.params.symbol_id; uplink_context ul_context = ul_context_repo.get(slot, symbol); if (ul_context.empty()) { - logger.info("Dropping Open Fronthaul message as no uplink slot context was found for slot={}, symbol={}", - results.params.slot, - results.params.symbol_id); + logger.debug("Dropping Open Fronthaul message as no uplink slot context was found for slot={}, symbol={}", + results.params.slot, + results.params.symbol_id); return; } diff --git a/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer.h b/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer.h index 165d01abfc..0de8f0023a 100644 --- a/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer.h +++ b/lib/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer.h @@ -22,7 +22,7 @@ #pragma once -#include "../support/uplink_context_repo.h" +#include "../support/uplink_context_repository.h" namespace srsran { namespace ofh { @@ -35,9 +35,9 @@ struct uplane_message_decoder_results; class uplane_rx_symbol_data_flow_writer { public: - uplane_rx_symbol_data_flow_writer(span ul_eaxc_, - srslog::basic_logger& logger_, - std::shared_ptr ul_context_repo_) : + uplane_rx_symbol_data_flow_writer(span ul_eaxc_, + srslog::basic_logger& logger_, + std::shared_ptr ul_context_repo_) : ul_eaxc(ul_eaxc_.begin(), ul_eaxc_.end()), logger(logger_), ul_context_repo_ptr(ul_context_repo_), @@ -53,8 +53,8 @@ class uplane_rx_symbol_data_flow_writer private: const static_vector ul_eaxc; srslog::basic_logger& logger; - std::shared_ptr ul_context_repo_ptr; - uplink_context_repo& ul_context_repo; + std::shared_ptr ul_context_repo_ptr; + uplink_context_repository& ul_context_repo; }; } // namespace ofh diff --git a/lib/ofh/receiver/ofh_uplane_uplink_packet_handler.cpp b/lib/ofh/receiver/ofh_uplane_uplink_packet_handler.cpp deleted file mode 100644 index 914e523860..0000000000 --- a/lib/ofh/receiver/ofh_uplane_uplink_packet_handler.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* - * - * Copyright 2021-2023 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "ofh_uplane_uplink_packet_handler.h" - -using namespace srsran; -using namespace ofh; - -uplane_uplink_packet_handler::uplane_uplink_packet_handler(uplane_uplink_packet_handler_config&& config) : - logger(config.logger), - is_prach_cp_enabled(config.is_prach_cp_enabled), - ul_prach_eaxc(config.ul_prach_eaxc), - ul_eaxc(config.ul_eaxc), - vlan_params(config.vlan_params), - uplane_decoder(std::move(config.uplane_decoder)), - ecpri_decoder(std::move(config.ecpri_decoder)), - eth_frame_decoder(std::move(config.eth_frame_decoder)), - cplane_repo_ptr(config.cplane_repo), - cplane_repo(*cplane_repo_ptr) -{ - srsran_assert(uplane_decoder, "Invalid User-Plane packet decoder"); - srsran_assert(ecpri_decoder, "Invalid eCPRI packet decoder"); - srsran_assert(eth_frame_decoder, "Invalid Ethernet frame decoder"); - srsran_assert(cplane_repo_ptr, "Invalid control plane repository"); -} - -slot_symbol_point uplane_uplink_packet_handler::peek_slot_symbol_point(span packet) const -{ - return uplane_decoder->peek_slot_symbol_point(packet); -} - -expected -uplane_uplink_packet_handler::decode_eth_and_ecpri_packet(span packet) -{ - ether::vlan_frame_params eth_params; - span ecpri_pdu = eth_frame_decoder->decode(packet, eth_params); - if (ecpri_pdu.empty() || should_ethernet_frame_be_filtered(eth_params)) { - return {default_error_t({})}; - } - - ecpri::packet_parameters ecpri_params; - span ofh_pdu = ecpri_decoder->decode(ecpri_pdu, ecpri_params); - if (ofh_pdu.empty() || should_ecpri_packet_be_filtered(ecpri_params)) { - return {default_error_t({})}; - } - - return {{variant_get(ecpri_params.type_params).pc_id, ofh_pdu}}; -} - -expected uplane_uplink_packet_handler::decode_ofh_packet(unsigned eaxc, - span packet) -{ - message_decoder_results results; - // Fill the eAxC so the Control-Plane context can be found in the repository. - results.eaxc = eaxc; - uplane_message_decoder_results& uplane_results = results.uplane_results; - if (!uplane_decoder->decode(uplane_results, packet)) { - return {default_error_t({})}; - } - - if (should_uplane_packet_be_filtered(results)) { - return {default_error_t({})}; - } - - return {std::move(results)}; -} - -bool uplane_uplink_packet_handler::should_ethernet_frame_be_filtered(const ether::vlan_frame_params& eth_params) const -{ - if (eth_params.mac_src_address != vlan_params.mac_src_address) { - logger.debug("Dropping Open Fronthaul User-Plane packet as source MAC addresses doesn't match(detected={:x}, " - "expected={:x})", - span(eth_params.mac_src_address), - span(vlan_params.mac_src_address)); - - return true; - } - - if (eth_params.mac_dst_address != vlan_params.mac_dst_address) { - logger.debug("Dropping Open Fronthaul User-Plane packet as destination MAC addresses doesn't match(detected={:x}, " - "expected={:x})", - span(eth_params.mac_dst_address), - span(vlan_params.mac_dst_address)); - - return true; - } - - if (eth_params.eth_type != vlan_params.eth_type) { - logger.debug("Dropping Open Fronthaul User-Plane packet as decoded Ethernet type is {} and it is expected {}", - eth_params.eth_type, - vlan_params.eth_type); - - return true; - } - - return false; -} - -bool uplane_uplink_packet_handler::should_ecpri_packet_be_filtered(const ecpri::packet_parameters& ecpri_params) const -{ - if (ecpri_params.header.msg_type != ecpri::message_type::iq_data) { - logger.debug("Dropping Open Fronthaul User-Plane packet as decoded eCPRI message type is not IQ data"); - - return true; - } - - const ecpri::iq_data_parameters& ecpri_iq_params = variant_get(ecpri_params.type_params); - if ((std::find(ul_eaxc.begin(), ul_eaxc.end(), ecpri_iq_params.pc_id) == ul_eaxc.end()) && - (std::find(ul_prach_eaxc.begin(), ul_prach_eaxc.end(), ecpri_iq_params.pc_id) == ul_prach_eaxc.end())) { - logger.debug("Dropping Open Fronthaul User-Plane packet as decoded eAxC is {}", ecpri_iq_params.pc_id); - - return true; - } - - // :TODO: check sequence id. - - return false; -} - -bool uplane_uplink_packet_handler::should_uplane_packet_be_filtered(const message_decoder_results& results) const -{ - const uplane_message_decoder_results& uplane_results = results.uplane_results; - if (uplane_results.params.filter_index == filter_index_type::reserved) { - logger.debug("Dropping Open Fronthaul User-Plane packet as decoded filter index={} for slot={}, symbol={}", - to_value(uplane_results.params.filter_index), - uplane_results.params.slot, - uplane_results.params.symbol_id); - - return true; - } - - // When Control-Plane message for PRACH is not configured, skip the check. - if (!is_prach_cp_enabled && is_a_prach_message(uplane_results.params.filter_index)) { - return false; - } - - const uplane_message_params& params = uplane_results.params; - expected ex_cp_context = - cplane_repo.get(params.slot, params.symbol_id, params.filter_index, results.eaxc); - - if (!ex_cp_context) { - logger.debug("Dropping Open Fronthaul User-Plane packet as no Control-Packet associated was found for slot={}, " - "symbol={}, eAxC={}", - params.slot, - params.symbol_id, - results.eaxc); - - return true; - } - - // Check the PRBs. - const ul_cplane_context& cp_context = ex_cp_context.value(); - for (const auto& up_section : uplane_results.sections) { - if (up_section.start_prb > MAX_NOF_PRBS - 1) { - logger.debug("Dropping Open Fronthaul User-Plane packet as the first PRB index {} is not valid", - up_section.start_prb); - - return true; - } - - if (up_section.start_prb + up_section.nof_prbs > MAX_NOF_PRBS) { - logger.debug("Dropping Open Fronthaul User-Plane packet as the last PRB index {} is not valid", - up_section.start_prb + up_section.nof_prbs); - - return true; - } - - if (!up_section.is_every_rb_used) { - logger.debug( - "Dropping Open Fronthaul User-Plane packet as 'every other resource block is used' mode is not supported"); - - return true; - } - - if (!up_section.use_current_symbol_number) { - logger.debug("Dropping Open Fronthaul User-Plane packet as 'increment the current symbol number and use that' " - "mode is not supported"); - - return true; - } - - if (up_section.start_prb < cp_context.prb_start || - (up_section.start_prb + up_section.nof_prbs) > (cp_context.prb_start + cp_context.nof_prb)) { - logger.debug("Dropping Open Fronthaul User-Plane packet as PRB indexes {}:{} don't match Control-Packet {}:{}.", - up_section.start_prb, - up_section.nof_prbs, - cp_context.prb_start, - cp_context.nof_prb); - return true; - } - } - - return false; -} diff --git a/lib/ofh/receiver/ofh_uplane_uplink_packet_handler.h b/lib/ofh/receiver/ofh_uplane_uplink_packet_handler.h deleted file mode 100644 index bf8d8b3916..0000000000 --- a/lib/ofh/receiver/ofh_uplane_uplink_packet_handler.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * - * Copyright 2021-2023 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#pragma once - -#include "../support/uplink_cplane_context_repository.h" -#include "srsran/adt/expected.h" -#include "srsran/ofh/ecpri/ecpri_packet_decoder.h" -#include "srsran/ofh/ethernet/vlan_ethernet_frame_decoder.h" -#include "srsran/ofh/ofh_constants.h" -#include "srsran/ofh/serdes/ofh_message_decoder_properties.h" -#include "srsran/ofh/serdes/ofh_uplane_message_decoder.h" - -namespace srsran { -namespace ofh { - -/// User-Plane uplink packet handler configuration. -struct uplane_uplink_packet_handler_config { - explicit uplane_uplink_packet_handler_config(srslog::basic_logger& logger_) : logger(logger_) {} - - /// Logger. - srslog::basic_logger& logger; - /// PRACH Control-Plane enabled flag. - bool is_prach_cp_enabled; - /// VLAN ethernet frame parameters. - ether::vlan_frame_params vlan_params; - /// Uplink PRACH eAxC. - static_vector ul_prach_eaxc; - /// Uplink eAxC. - static_vector ul_eaxc; - /// User-Plane message decoder. - std::unique_ptr uplane_decoder; - /// eCPRI packet decoder. - std::unique_ptr ecpri_decoder; - /// Ethernet frame decoder. - std::unique_ptr eth_frame_decoder; - /// Uplink Control-Plane context repository. - std::shared_ptr cplane_repo; -}; - -/// Ethernet and eCPRI decoding results. -struct eth_and_ecpri_decoding_results { - /// Decoded eAxC. - unsigned eaxc; - /// Open Fronthaul packet. - span ofh_packet; -}; - -/// Open Fronthaul uplink packet handler. -class uplane_uplink_packet_handler -{ -public: - explicit uplane_uplink_packet_handler(uplane_uplink_packet_handler_config&& config); - - /// Decodes the given packet and returns the results. - expected decode_eth_and_ecpri_packet(span packet); - - /// Decodes the given packet and returns the results. - expected decode_ofh_packet(unsigned eaxc, span packet); - - /// Peeks and returns the slot symbol point of the given packet. - slot_symbol_point peek_slot_symbol_point(span packet) const; - -private: - /// Returns true if the ethernet frame represented by the given eth parameters should be filtered, otherwise false. - bool should_ethernet_frame_be_filtered(const ether::vlan_frame_params& eth_params) const; - - /// Returns true if the eCPRI packet represented by the given eCPRI parameters should be filtered, otherwise false. - bool should_ecpri_packet_be_filtered(const ecpri::packet_parameters& ecpri_params) const; - - /// Returns true if the User-Plane packet represented by the given User-Plane results should be filtered, otherwise - /// false. - bool should_uplane_packet_be_filtered(const message_decoder_results& results) const; - -private: - srslog::basic_logger& logger; - const bool is_prach_cp_enabled; - const static_vector ul_prach_eaxc; - const static_vector ul_eaxc; - ether::vlan_frame_params vlan_params; - std::unique_ptr uplane_decoder; - std::unique_ptr ecpri_decoder; - std::unique_ptr eth_frame_decoder; - std::shared_ptr cplane_repo_ptr; - uplink_cplane_context_repository& cplane_repo; -}; - -} // namespace ofh -} // namespace srsran diff --git a/lib/ofh/receiver/ofh_uplane_uplink_symbol_manager.cpp b/lib/ofh/receiver/ofh_uplane_uplink_symbol_manager.cpp deleted file mode 100644 index b63f030fbd..0000000000 --- a/lib/ofh/receiver/ofh_uplane_uplink_symbol_manager.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * - * Copyright 2021-2023 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "ofh_uplane_uplink_symbol_manager.h" - -using namespace srsran; -using namespace ofh; - -uplane_uplink_symbol_manager::uplane_uplink_symbol_manager(const uplane_uplink_symbol_manager_config& config) : - logger(config.logger), - ul_eaxc(config.ul_eaxc), - prach_eaxc(config.prach_eaxc), - prach_repo_ptr(config.prach_repo), - ul_slot_repo_ptr(config.ul_slot_repo), - notifier(config.notifier), - packet_handler(config.packet_handler), - prach_repo(*prach_repo_ptr), - ul_slot_repo(*ul_slot_repo_ptr), - window_checker(config.rx_window) -{ - srsran_assert(prach_repo_ptr, "Invalid PRACH repository"); - srsran_assert(ul_slot_repo_ptr, "Invalid UL slot repository"); -} - -void uplane_uplink_symbol_manager::on_new_frame(span payload) -{ - expected decoding_results = packet_handler.decode_eth_and_ecpri_packet(payload); - - // Do nothing on decoding error. - if (decoding_results.is_error()) { - return; - } - - // Fill the reception window statistics. - window_checker.update_rx_window_statistics( - packet_handler.peek_slot_symbol_point(decoding_results.value().ofh_packet)); - - handle_ofh_decoding(decoding_results.value().eaxc, decoding_results.value().ofh_packet); -} - -void uplane_uplink_symbol_manager::handle_ofh_decoding(unsigned eaxc, span payload) -{ - expected decoding_results = packet_handler.decode_ofh_packet(eaxc, payload); - - // Do nothing on decoding error. - if (decoding_results.is_error()) { - return; - } - - decoding_results.value().eaxc = eaxc; - const uplane_message_decoder_results& results = decoding_results.value().uplane_results; - - // Copy the PRBs into the PRACH buffer. - if (is_a_prach_message(results.params.filter_index)) { - handle_prach_prbs(decoding_results.value()); - - return; - } - - // Copy the PRBs into the resource grid. - if (results.params.filter_index == filter_index_type::standard_channel_filter) { - handle_grid_prbs(decoding_results.value()); - - return; - } -} - -void uplane_uplink_symbol_manager::handle_prach_prbs(const message_decoder_results& results) -{ - const uplane_message_decoder_results& uplane_results = results.uplane_results; - slot_point slot = uplane_results.params.slot; - - ul_prach_context prach_context = prach_repo.get(slot); - if (prach_context.empty()) { - logger.debug("Dropping Open Fronthaul message as no uplink PRACH context was found for slot={}, symbol={}", - slot, - uplane_results.params.symbol_id); - return; - } - const uplane_section_params& sect_params = uplane_results.sections.front(); - - if (sect_params.nof_prbs * NOF_SUBCARRIERS_PER_RB < prach_context.get_prach_nof_re()) { - logger.error("PRACH message segmentation not supported"); - - return; - } - - // Find resource grid port with eAxC. - unsigned port = std::distance(prach_eaxc.begin(), std::find(prach_eaxc.begin(), prach_eaxc.end(), results.eaxc)); - logger.debug("Handling PRACH in slot {}: port={}, symbol={}", slot, port, uplane_results.params.symbol_id); - - bool notified = prach_repo.update_buffer_and_notify( - slot, port, uplane_results.params.symbol_id, sect_params.iq_samples, notifier); - if (notified) { - logger.debug("Finished PRACH reception in slot {}", slot); - } -} - -void uplane_uplink_symbol_manager::handle_grid_prbs(const message_decoder_results& results) -{ - const uplane_message_decoder_results& uplane_results = results.uplane_results; - const slot_point slot = uplane_results.params.slot; - ul_slot_context ul_data_context = ul_slot_repo.get(slot); - if (ul_data_context.empty()) { - logger.info("Dropping Open Fronthaul message as no uplink slot context was found for slot={}, symbol={}", - uplane_results.params.slot, - uplane_results.params.symbol_id); - - return; - } - - // Find resource grid port with eAxC. - unsigned rg_port = std::distance(ul_eaxc.begin(), std::find(ul_eaxc.begin(), ul_eaxc.end(), results.eaxc)); - - const unsigned du_ul_nof_prbs = ul_data_context.get_grid_nof_prbs(); - const unsigned symbol = uplane_results.params.symbol_id; - for (const auto& sect : uplane_results.sections) { - // Section PRBs are above the last PRB of the DU. Do not copy. - if (sect.start_prb >= du_ul_nof_prbs) { - continue; - } - - // By default, try to copy all the expected PRBs. - unsigned nof_prbs_to_write = du_ul_nof_prbs - sect.start_prb; - - // Section contains less PRBs than the grid. Copy the whole section. - if (sect.start_prb + sect.nof_prbs < du_ul_nof_prbs) { - nof_prbs_to_write = sect.nof_prbs; - } - - srsran_assert(rg_port < ul_eaxc.size(), "Invalid resource grid port={}", rg_port); - - ul_slot_repo.update_grid_and_notify( - slot, - rg_port, - symbol, - sect.start_prb * NOF_SUBCARRIERS_PER_RB, - span(sect.iq_samples) - .subspan(sect.start_prb * NOF_SUBCARRIERS_PER_RB, nof_prbs_to_write * NOF_SUBCARRIERS_PER_RB), - notifier); - } -} diff --git a/lib/ofh/receiver/ofh_uplane_uplink_symbol_manager.h b/lib/ofh/receiver/ofh_uplane_uplink_symbol_manager.h deleted file mode 100644 index 10fa1a4870..0000000000 --- a/lib/ofh/receiver/ofh_uplane_uplink_symbol_manager.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * - * Copyright 2021-2023 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#pragma once - -#include "../support/uplink_context_repository.h" -#include "ofh_rx_window_checker.h" -#include "ofh_uplane_uplink_packet_handler.h" -#include "srsran/adt/static_vector.h" -#include "srsran/ofh/ethernet/ethernet_frame_notifier.h" -#include "srsran/ofh/ofh_uplane_rx_symbol_notifier.h" -#include "srsran/ofh/serdes/ofh_uplane_message_decoder.h" - -namespace srsran { -namespace ofh { - -/// User-Plane uplink symbol manager configuration. -struct uplane_uplink_symbol_manager_config { - uplane_uplink_symbol_manager_config(srslog::basic_logger& logger_, - uplane_rx_symbol_notifier& notifier_, - uplane_uplink_packet_handler& packet_handler_, - std::shared_ptr> prach_repo_, - std::shared_ptr> ul_slot_repo_, - const static_vector& ul_eaxc_, - const static_vector& prach_eaxc_, - rx_window_checker& rx_window_) : - logger(logger_), - notifier(notifier_), - packet_handler(packet_handler_), - rx_window(rx_window_), - prach_repo(prach_repo_), - ul_slot_repo(ul_slot_repo_), - ul_eaxc(ul_eaxc_), - prach_eaxc(prach_eaxc_) - { - } - - /// Logger. - srslog::basic_logger& logger; - /// User-Plane receive symbol notifier. - uplane_rx_symbol_notifier& notifier; - /// User-Plane uplink packet handler. - uplane_uplink_packet_handler& packet_handler; - /// Reception window checker. - rx_window_checker& rx_window; - /// PRACH context repository. - std::shared_ptr> prach_repo; - /// Uplink slot context repository. - std::shared_ptr> ul_slot_repo; - /// Uplink eAxC. - static_vector ul_eaxc; - /// PRACH eAxC. - static_vector prach_eaxc; -}; - -/// User-Plane uplink symbol manager. -class uplane_uplink_symbol_manager : public ether::frame_notifier -{ -public: - explicit uplane_uplink_symbol_manager(const uplane_uplink_symbol_manager_config& config); - - /// Handles the given User-Plane decoder results. - void on_new_frame(span payload) override; - -private: - /// Handles the Open Fronthaul decoding. - void handle_ofh_decoding(unsigned eaxc, span payload); - - /// Handles the PRACH PRBs given in the results. - void handle_prach_prbs(const message_decoder_results& results); - - /// Handles the uplink grid PRBs given in the results. - void handle_grid_prbs(const message_decoder_results& results); - -private: - srslog::basic_logger& logger; - const static_vector ul_eaxc; - const static_vector prach_eaxc; - std::shared_ptr> prach_repo_ptr; - std::shared_ptr> ul_slot_repo_ptr; - uplane_rx_symbol_notifier& notifier; - uplane_uplink_packet_handler& packet_handler; - uplink_context_repository& prach_repo; - uplink_context_repository& ul_slot_repo; - rx_window_checker& window_checker; -}; - -} // namespace ofh -} // namespace srsran diff --git a/lib/ofh/serdes/CMakeLists.txt b/lib/ofh/serdes/CMakeLists.txt index 387b63c652..46ae03cc21 100644 --- a/lib/ofh/serdes/CMakeLists.txt +++ b/lib/ofh/serdes/CMakeLists.txt @@ -22,6 +22,7 @@ set(SOURCES ofh_cplane_message_builder_dynamic_compression_impl.cpp ofh_cplane_message_builder_impl.cpp ofh_cplane_message_builder_static_compression_impl.cpp + ofh_serdes_factories.cpp ofh_uplane_message_builder_impl.cpp ofh_uplane_message_builder_dynamic_compression_impl.cpp ofh_uplane_message_builder_static_compression_impl.cpp diff --git a/lib/ofh/serdes/ofh_serdes_factories.cpp b/lib/ofh/serdes/ofh_serdes_factories.cpp new file mode 100644 index 0000000000..07438c346f --- /dev/null +++ b/lib/ofh/serdes/ofh_serdes_factories.cpp @@ -0,0 +1,80 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srsran/ofh/serdes/ofh_serdes_factories.h" +#include "ofh_cplane_message_builder_dynamic_compression_impl.h" +#include "ofh_cplane_message_builder_static_compression_impl.h" +#include "ofh_uplane_message_builder_dynamic_compression_impl.h" +#include "ofh_uplane_message_builder_static_compression_impl.h" +#include "ofh_uplane_message_decoder_dynamic_compression_impl.h" +#include "ofh_uplane_message_decoder_static_compression_impl.h" + +using namespace srsran; +using namespace ofh; + +std::unique_ptr srsran::ofh::create_ofh_control_plane_static_compression_message_builder() +{ + return std::make_unique(); +} + +std::unique_ptr srsran::ofh::create_ofh_control_plane_dynamic_compression_message_builder() +{ + return std::make_unique(); +} + +std::unique_ptr +srsran::ofh::create_static_compr_method_ofh_user_plane_packet_builder(srslog::basic_logger& logger, + iq_compressor& compressor) +{ + return std::make_unique(logger, compressor); +} + +std::unique_ptr +srsran::ofh::create_dynamic_compr_method_ofh_user_plane_packet_builder(srslog::basic_logger& logger, + iq_compressor& compressor) +{ + return std::make_unique(logger, compressor); +} + +std::unique_ptr +srsran::ofh::create_static_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, + subcarrier_spacing scs, + cyclic_prefix cp, + unsigned ru_nof_prbs, + std::unique_ptr decompressor, + const ru_compression_params& compr_params, + const ru_compression_params& prach_compr_params) +{ + return std::make_unique( + logger, scs, get_nsymb_per_slot(cp), ru_nof_prbs, std::move(decompressor), compr_params, prach_compr_params); +} + +std::unique_ptr +srsran::ofh::create_dynamic_compr_method_ofh_user_plane_packet_decoder(srslog::basic_logger& logger, + subcarrier_spacing scs, + cyclic_prefix cp, + unsigned ru_nof_prbs, + std::unique_ptr decompressor) +{ + return std::make_unique( + logger, scs, get_nsymb_per_slot(cp), ru_nof_prbs, std::move(decompressor)); +} diff --git a/lib/ofh/serdes/ofh_uplane_message_decoder_dynamic_compression_impl.h b/lib/ofh/serdes/ofh_uplane_message_decoder_dynamic_compression_impl.h index 8139a1f198..2a17a09d20 100644 --- a/lib/ofh/serdes/ofh_uplane_message_decoder_dynamic_compression_impl.h +++ b/lib/ofh/serdes/ofh_uplane_message_decoder_dynamic_compression_impl.h @@ -35,12 +35,12 @@ class network_order_binary_deserializer; class uplane_message_decoder_dynamic_compression_impl : public uplane_message_decoder_impl { public: - explicit uplane_message_decoder_dynamic_compression_impl(srslog::basic_logger& logger_, - subcarrier_spacing scs_, - unsigned nof_symbols_, - unsigned ru_nof_prbs_, - iq_decompressor& decompressor_) : - uplane_message_decoder_impl(logger_, scs_, nof_symbols_, ru_nof_prbs_, decompressor_) + explicit uplane_message_decoder_dynamic_compression_impl(srslog::basic_logger& logger_, + subcarrier_spacing scs_, + unsigned nof_symbols_, + unsigned ru_nof_prbs_, + std::unique_ptr decompressor_) : + uplane_message_decoder_impl(logger_, scs_, nof_symbols_, ru_nof_prbs_, std::move(decompressor_)) { } diff --git a/lib/ofh/serdes/ofh_uplane_message_decoder_impl.cpp b/lib/ofh/serdes/ofh_uplane_message_decoder_impl.cpp index de7d0cb470..807d736406 100644 --- a/lib/ofh/serdes/ofh_uplane_message_decoder_impl.cpp +++ b/lib/ofh/serdes/ofh_uplane_message_decoder_impl.cpp @@ -321,7 +321,7 @@ bool uplane_message_decoder_impl::decode_iq_data(uplane_section_params& // Decompress the samples. results.iq_samples.resize(results.nof_prbs * NOF_SUBCARRIERS_PER_RB); - decompressor.decompress(results.iq_samples, comp_prbs, compression_params); + decompressor->decompress(results.iq_samples, comp_prbs, compression_params); return true; } diff --git a/lib/ofh/serdes/ofh_uplane_message_decoder_impl.h b/lib/ofh/serdes/ofh_uplane_message_decoder_impl.h index c0ef511314..96c23803c7 100644 --- a/lib/ofh/serdes/ofh_uplane_message_decoder_impl.h +++ b/lib/ofh/serdes/ofh_uplane_message_decoder_impl.h @@ -22,6 +22,7 @@ #pragma once +#include "srsran/ofh/compression/iq_decompressor.h" #include "srsran/ofh/serdes/ofh_uplane_message_decoder.h" #include "srsran/srslog/logger.h" @@ -35,13 +36,18 @@ class network_order_binary_deserializer; class uplane_message_decoder_impl : public uplane_message_decoder { public: - uplane_message_decoder_impl(srslog::basic_logger& logger_, - subcarrier_spacing scs_, - unsigned nof_symbols_, - unsigned ru_nof_prbs_, - iq_decompressor& decompressor_) : - logger(logger_), decompressor(decompressor_), scs(scs_), nof_symbols(nof_symbols_), ru_nof_prbs(ru_nof_prbs_) + uplane_message_decoder_impl(srslog::basic_logger& logger_, + subcarrier_spacing scs_, + unsigned nof_symbols_, + unsigned ru_nof_prbs_, + std::unique_ptr decompressor_) : + logger(logger_), + decompressor(std::move(decompressor_)), + scs(scs_), + nof_symbols(nof_symbols_), + ru_nof_prbs(ru_nof_prbs_) { + srsran_assert(decompressor, "Invalid IQ decompressor"); } // See interface for documentation. @@ -82,11 +88,11 @@ class uplane_message_decoder_impl : public uplane_message_decoder bool is_a_prach_msg) = 0; protected: - srslog::basic_logger& logger; - iq_decompressor& decompressor; - const subcarrier_spacing scs; - const unsigned nof_symbols; - const unsigned ru_nof_prbs; + srslog::basic_logger& logger; + std::unique_ptr decompressor; + const subcarrier_spacing scs; + const unsigned nof_symbols; + const unsigned ru_nof_prbs; }; } // namespace ofh diff --git a/lib/ofh/serdes/ofh_uplane_message_decoder_static_compression_impl.cpp b/lib/ofh/serdes/ofh_uplane_message_decoder_static_compression_impl.cpp index 176e856255..ef3fe04033 100644 --- a/lib/ofh/serdes/ofh_uplane_message_decoder_static_compression_impl.cpp +++ b/lib/ofh/serdes/ofh_uplane_message_decoder_static_compression_impl.cpp @@ -38,14 +38,14 @@ bool uplane_message_decoder_static_compression_impl::decode_compression_header( } uplane_message_decoder_static_compression_impl::uplane_message_decoder_static_compression_impl( - srslog::basic_logger& logger_, - subcarrier_spacing scs_, - unsigned nof_symbols_, - unsigned ru_nof_prbs_, - iq_decompressor& decompressor_, - const ru_compression_params& compression_params_, - const ru_compression_params& prach_compression_params_) : - uplane_message_decoder_impl(logger_, scs_, nof_symbols_, ru_nof_prbs_, decompressor_), + srslog::basic_logger& logger_, + subcarrier_spacing scs_, + unsigned nof_symbols_, + unsigned ru_nof_prbs_, + std::unique_ptr decompressor_, + const ru_compression_params& compression_params_, + const ru_compression_params& prach_compression_params_) : + uplane_message_decoder_impl(logger_, scs_, nof_symbols_, ru_nof_prbs_, std::move(decompressor_)), compression_params(compression_params_), prach_compression_params(prach_compression_params_) { diff --git a/lib/ofh/serdes/ofh_uplane_message_decoder_static_compression_impl.h b/lib/ofh/serdes/ofh_uplane_message_decoder_static_compression_impl.h index 82739e122f..1797723d65 100644 --- a/lib/ofh/serdes/ofh_uplane_message_decoder_static_compression_impl.h +++ b/lib/ofh/serdes/ofh_uplane_message_decoder_static_compression_impl.h @@ -34,13 +34,13 @@ class network_order_binary_deserializer; class uplane_message_decoder_static_compression_impl : public uplane_message_decoder_impl { public: - explicit uplane_message_decoder_static_compression_impl(srslog::basic_logger& logger_, - subcarrier_spacing scs_, - unsigned nof_symbols_, - unsigned ru_nof_prbs_, - iq_decompressor& decompressor_, - const ru_compression_params& compression_params_, - const ru_compression_params& prach_compression_params_); + explicit uplane_message_decoder_static_compression_impl(srslog::basic_logger& logger_, + subcarrier_spacing scs_, + unsigned nof_symbols_, + unsigned ru_nof_prbs_, + std::unique_ptr decompressor_, + const ru_compression_params& compression_params_, + const ru_compression_params& prach_compression_params_); private: // See parent for documentation. diff --git a/lib/ofh/support/prach_context_repository.h b/lib/ofh/support/prach_context_repository.h index 832328ec9e..c1155669f7 100644 --- a/lib/ofh/support/prach_context_repository.h +++ b/lib/ofh/support/prach_context_repository.h @@ -111,15 +111,16 @@ class prach_context return buffer_stats[symbol].re_written; } - /// Returns the number of symbols used by the PRACH associated with the stored context. - unsigned get_prach_nof_symbols() const { return empty() ? 0U : nof_symbols; } - /// Writes the given IQ buffer corresponding to the given symbol and port. void write_iq(unsigned port, unsigned symbol, unsigned re_start, span iq_buffer) { srsran_assert(context_info.buffer, "No valid PRACH buffer in the context"); srsran_assert(symbol < nof_symbols, "Invalid symbol index"); - srsran_assert(port < buffer_stats[symbol].re_written.size(), "Invalid port index"); + + // Skip writing if the given port does not fit in the PRACH buffer. + if (port >= nof_ports) { + return; + } // Update the buffer. span prach_out_buffer = context_info.buffer->get_symbol( @@ -196,10 +197,13 @@ class prach_context_repository explicit prach_context_repository(unsigned size_) : buffer(size_) {} /// Adds the given entry to the repository at slot. - void add(const prach_buffer_context& context, prach_buffer& buffer_, unsigned nof_ports = 1) + void add(const prach_buffer_context& context, + prach_buffer& buffer_, + unsigned nof_ports = 1, + slot_point slot = slot_point()) { std::lock_guard lock(mutex); - entry(context.slot) = prach_context(context, buffer_, nof_ports); + entry(slot.valid() ? slot : context.slot) = prach_context(context, buffer_, nof_ports); } /// Function to write the uplink PRACH buffer. diff --git a/lib/ofh/support/uplink_context_repo.h b/lib/ofh/support/uplink_context_repo.h deleted file mode 100644 index 453d894e53..0000000000 --- a/lib/ofh/support/uplink_context_repo.h +++ /dev/null @@ -1,180 +0,0 @@ -/* - * - * Copyright 2021-2023 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#pragma once - -#include "srsran/adt/expected.h" -#include "srsran/ofh/ofh_constants.h" -#include "srsran/ofh/ofh_uplane_rx_symbol_notifier.h" -#include "srsran/ofh/slot_symbol_point.h" -#include "srsran/phy/support/resource_grid.h" -#include "srsran/phy/support/resource_grid_context.h" -#include "srsran/phy/support/resource_grid_reader.h" -#include "srsran/phy/support/resource_grid_writer.h" -#include "srsran/ran/cyclic_prefix.h" -#include "srsran/ran/resource_block.h" -#include - -namespace srsran { -namespace ofh { - -/// Uplink context. -class uplink_context -{ -public: - /// Information related to the resource grid stored in the uplink context. - struct uplink_context_resource_grid_info { - resource_grid_context context; - resource_grid* grid = nullptr; - }; - - /// Default constructor. - uplink_context() = default; - - /// Constructs an uplink slot context with the given resource grid and resource grid context. - uplink_context(unsigned symbol_, const resource_grid_context& context_, resource_grid& grid_) : - symbol(symbol_), grid({context_, &grid_}) - { - re_written = static_vector, MAX_NOF_SUPPORTED_EAXC>( - grid_.get_writer().get_nof_ports(), - bounded_bitset(size_t(grid_.get_writer().get_nof_subc()))); - } - - /// Returns true if this context is empty, otherwise false. - bool empty() const { return grid.grid == nullptr; } - - /// Returns the number of PRBs of the context grid or zero if no grid was configured for this context. - unsigned get_grid_nof_prbs() const - { - return (grid.grid) ? (grid.grid->get_writer().get_nof_subc() / NOF_SUBCARRIERS_PER_RB) : 0U; - } - - /// Returns a span of bitmaps that indicate the REs that have been written for the given symbol. Each element of the - /// span corresponds to a port. - span> get_re_written_mask() const { return re_written; } - - /// Writes the given RE IQ buffer into the port and start RE. - void write_grid(unsigned port, unsigned start_re, span re_iq_buffer) - { - srsran_assert(grid.grid, "Invalid resource grid"); - srsran_assert(port < re_written.size(), "Invalid port"); - - grid.grid->get_writer().put(port, symbol, start_re, re_iq_buffer); - re_written[port].fill(start_re, start_re + re_iq_buffer.size()); - } - - /// Tries to get a complete resource grid. A resource grid is considered completed when all the PRBs for all the ports - /// have been written. - expected try_getting_complete_resource_grid() const - { - if (!grid.grid) { - return default_error_t({}); - } - - if (!have_all_prbs_been_written()) { - return default_error_t({}); - } - - return {grid}; - } - -private: - /// Returns true when all the REs for the current symbol have been written. - bool have_all_prbs_been_written() const - { - return std::all_of( - re_written.begin(), re_written.end(), [](const auto& port_re_written) { return port_re_written.all(); }); - } - -private: - unsigned symbol; - uplink_context_resource_grid_info grid; - static_vector, MAX_NOF_SUPPORTED_EAXC> re_written; -}; - -/// Uplink context repository. -class uplink_context_repo -{ - /// System frame number maximum value in this repository. - static constexpr unsigned SFN_MAX_VALUE = 1U << 8; - - std::vector> buffer; - //: TODO: make this lock free - mutable std::mutex mutex; - - /// Returns the entry of the repository for the given slot and symbol. - uplink_context& entry(slot_point slot, unsigned symbol) - { - srsran_assert(symbol < MAX_NSYMB_PER_SLOT, "Invalid symbol index={}", symbol); - - slot_point entry_slot(slot.numerology(), slot.sfn() % SFN_MAX_VALUE, slot.slot_index()); - unsigned index = entry_slot.system_slot() % buffer.size(); - return buffer[index][symbol]; - } - - /// Returns the entry of the repository for the given slot and symbol. - const uplink_context& entry(slot_point slot, unsigned symbol) const - { - srsran_assert(symbol < MAX_NSYMB_PER_SLOT, "Invalid symbol index={}", symbol); - - slot_point entry_slot(slot.numerology(), slot.sfn() % SFN_MAX_VALUE, slot.slot_index()); - unsigned index = entry_slot.system_slot() % buffer.size(); - return buffer[index][symbol]; - } - -public: - explicit uplink_context_repo(unsigned size_) : buffer(size_) {} - - /// Adds the given entry to the repository at slot. - void add(const resource_grid_context& context, resource_grid& grid) - { - std::lock_guard lock(mutex); - for (unsigned symbol_id = 0, symbol_end = grid.get_reader().get_nof_symbols(); symbol_id != symbol_end; - ++symbol_id) { - entry(context.slot, symbol_id) = uplink_context(symbol_id, context, grid); - } - } - - /// Writes to the grid at the given slot, port, symbol and start resource element the given IQ buffer. - void write_grid(slot_point slot, unsigned port, unsigned symbol, unsigned start_re, span re_iq_buffer) - { - std::lock_guard lock(mutex); - entry(slot, symbol).write_grid(port, start_re, re_iq_buffer); - } - - /// Returns the entry of the repository for the given slot and symbol. - uplink_context get(slot_point slot, unsigned symbol) const - { - std::lock_guard lock(mutex); - return entry(slot, symbol); - } - - /// Clears the repository entry for the given slot and symbol. - void clear(slot_point slot, unsigned symbol) - { - std::lock_guard lock(mutex); - entry(slot, symbol) = {}; - } -}; - -} // namespace ofh -} // namespace srsran diff --git a/lib/ofh/support/uplink_context_repository.h b/lib/ofh/support/uplink_context_repository.h index d2df653a64..440c5d7fb0 100644 --- a/lib/ofh/support/uplink_context_repository.h +++ b/lib/ofh/support/uplink_context_repository.h @@ -22,333 +22,161 @@ #pragma once -#include "srsran/adt/static_vector.h" +#include "srsran/adt/expected.h" #include "srsran/ofh/ofh_constants.h" #include "srsran/ofh/ofh_uplane_rx_symbol_notifier.h" -#include "srsran/phy/support/prach_buffer.h" -#include "srsran/phy/support/prach_buffer_context.h" +#include "srsran/ofh/slot_symbol_point.h" +#include "srsran/phy/support/resource_grid.h" #include "srsran/phy/support/resource_grid_context.h" +#include "srsran/phy/support/resource_grid_reader.h" #include "srsran/phy/support/resource_grid_writer.h" #include "srsran/ran/cyclic_prefix.h" -#include "srsran/ran/prach/prach_frequency_mapping.h" -#include "srsran/ran/prach/prach_preamble_information.h" #include "srsran/ran/resource_block.h" #include namespace srsran { namespace ofh { -/// Uplink PRACH context. -class ul_prach_context +/// Uplink context. +class uplink_context { - /// PRACH buffer writer statistics. - struct prach_buffer_writer_stats { - /// Number of REs of the grid. - unsigned buffer_nof_re = 0; - /// Number of REs written indexed by port. - static_vector re_written; - - /// Returns true when all the RE for all the ports have been written. - explicit operator bool() const noexcept { return buffer_nof_re != 0; } - - /// Returns true when all the REs for the current symbol have been written. - bool have_all_res_been_written() const - { - return std::all_of(re_written.begin(), re_written.end(), [this](unsigned port_re_written) { - return port_re_written == this->buffer_nof_re; - }); - } - }; - public: - /// Default constructor. - ul_prach_context() = default; - - /// Constructs an uplink PRACH context with the given PRACH buffer and PRACH buffer context. - ul_prach_context(const prach_buffer_context& context_, prach_buffer& buffer_, unsigned nof_ports_) : - context(context_), buffer(&buffer_), nof_symbols(get_preamble_duration(context.format)), nof_ports(nof_ports_) - { - srsran_assert(context.nof_fd_occasions == 1, "Only supporting one frequency domain occasion"); - srsran_assert(context.nof_td_occasions == 1, "Only supporting one time domain occasion"); - - // Get preamble information. - preamble_info = - is_long_preamble(context.format) - ? get_prach_preamble_long_info(context.format) - : get_prach_preamble_short_info(context.format, to_ra_subcarrier_spacing(context.pusch_scs), true); - - freq_mapping_info = prach_frequency_mapping_get(preamble_info.scs, context.pusch_scs); - - if (!nof_symbols) { - nof_symbols = 1; - } - // Initialize statistic. - buffer_stats.buffer_nof_re = (preamble_info.sequence_length * nof_symbols); - buffer_stats.re_written = static_vector(nof_ports, 0); - } - - /// Returns true if this context is empty, otherwise false. - bool empty() const { return buffer == nullptr; } - - /// Returns the number of REs of one PRACH repetition or zero if no PRACH buffer is associated with this context. - unsigned get_prach_nof_re() const { return empty() ? 0U : (preamble_info.sequence_length + freq_mapping_info.k_bar); } - - /// Returns the number of symbols used by the PRACH associated with the stored context. - unsigned get_prach_nof_symbols() const { return empty() ? 0U : nof_symbols; } - - /// Writes the given IQ buffer corresponding to the given symbol and port and notifies that an uplink PRACH buffer is - /// ready when all the PRBs for all the symbols and ports have been written in the buffer. - bool update_buffer_and_notify(unsigned port, - unsigned symbol, - span iq_buffer, - uplane_rx_symbol_notifier& notifier) - { - srsran_assert(buffer, "No valid PRACH buffer in the context"); - srsran_assert(symbol < nof_symbols, "Invalid symbol index"); - - if (!buffer_stats) { - return false; - } - - // Skip writting if the given port does not fit in the PRACH buffer. - if (port >= nof_ports) { - return false; - } - - // Update the buffer. - span prach_out_buffer = - buffer->get_symbol(port, context.nof_fd_occasions - 1, context.nof_td_occasions - 1, symbol); - - unsigned nof_re_to_prach_data = freq_mapping_info.k_bar; - // Grab the data. - span prach_in_data = iq_buffer.subspan(nof_re_to_prach_data, preamble_info.sequence_length); - std::copy(prach_in_data.begin(), prach_in_data.end(), prach_out_buffer.begin()); - // Update statistics. - buffer_stats.re_written[port] += prach_in_data.size(); - - // Notify when PRACH buffer is ready. - if (!buffer_stats || !buffer_stats.have_all_res_been_written()) { - return false; - } - notifier.on_new_prach_window_data(context, *buffer); - - // Mark the symbol as sent. - buffer_stats = {}; - return true; - } - -private: - /// PRACH buffer context. - prach_buffer_context context; - /// PRACH buffer. - prach_buffer* buffer = nullptr; - /// Statistic of written data. - prach_buffer_writer_stats buffer_stats; - /// Preamble info of the PRACH associated with the stored context. - prach_preamble_information preamble_info; - /// Stored PRACH frequency mapping. - prach_frequency_mapping_information freq_mapping_info; - /// Number of OFDM symbols used by the stored PRACH. - unsigned nof_symbols; - /// Number of PRACH ports. - unsigned nof_ports; -}; - -/// Uplink slot context. -class ul_slot_context -{ - /// Resource grid writer statistics. - struct resource_grid_writer_stats { - /// Number of REs of the grid. - unsigned grid_nof_re = 0; - /// Number of REs written indexed by port. - static_vector re_written; - - /// Returns true when all the RE for all the ports have been written. - explicit operator bool() const noexcept { return grid_nof_re != 0; } - - /// Returns true when all the REs for the current symbol have been written. - bool have_all_prbs_been_written() const - { - return std::all_of(re_written.begin(), re_written.end(), [&](unsigned port_re_written) { - return port_re_written == grid_nof_re; - }); - } + /// Information related to the resource grid stored in the uplink context. + struct uplink_context_resource_grid_info { + resource_grid_context context; + resource_grid* grid = nullptr; }; -public: /// Default constructor. - ul_slot_context() = default; + uplink_context() = default; /// Constructs an uplink slot context with the given resource grid and resource grid context. - ul_slot_context(const resource_grid_context& context_, resource_grid& grid_) : context(context_), grid(&grid_) + uplink_context(unsigned symbol_, const resource_grid_context& context_, resource_grid& grid_) : + symbol(symbol_), grid({context_, &grid_}) { - for (unsigned symbol_id = 0, e = grid_.get_writer().get_nof_symbols(); symbol_id != e; ++symbol_id) { - rg_stats[symbol_id].grid_nof_re = grid_.get_writer().get_nof_subc(); - rg_stats[symbol_id].re_written = - static_vector(grid_.get_writer().get_nof_ports(), 0); - } + re_written = static_vector, MAX_NOF_SUPPORTED_EAXC>( + grid_.get_writer().get_nof_ports(), + bounded_bitset(size_t(grid_.get_writer().get_nof_subc()))); } /// Returns true if this context is empty, otherwise false. - bool empty() const { return grid == nullptr; } + bool empty() const { return grid.grid == nullptr; } /// Returns the number of PRBs of the context grid or zero if no grid was configured for this context. unsigned get_grid_nof_prbs() const { - return (grid) ? (grid->get_writer().get_nof_subc() / NOF_SUBCARRIERS_PER_RB) : 0U; + return (grid.grid) ? (grid.grid->get_writer().get_nof_subc() / NOF_SUBCARRIERS_PER_RB) : 0U; } - /// Writes the given RE IQ buffer into the given symbol, port and start RE and notifies that an uplink symbol has been - /// completed when all the PRBs for all the ports have been written in the grid. - void update_grid_and_notify(unsigned port, - unsigned symbol, - unsigned start_re, - span re_iq_buffer, - uplane_rx_symbol_notifier& notifier) - { - srsran_assert(grid, "Invalid resource grid"); - - // Update the grid. - write_grid(port, symbol, start_re, re_iq_buffer); + /// Returns a span of bitmaps that indicate the REs that have been written for the given symbol. Each element of the + /// span corresponds to a port. + span> get_re_written_mask() const { return re_written; } - // Notify the received symbol when it is complete. - notify_symbol_when_complete(symbol, notifier); - } - - /// Notify using the given notifier. - void notify_symbol(unsigned symbol, uplane_rx_symbol_notifier& notifier) + /// Writes the given RE IQ buffer into the port and start RE. + void write_grid(unsigned port, unsigned start_re, span re_iq_buffer) { - if (!rg_stats[symbol]) { + srsran_assert(grid.grid, "Invalid resource grid"); + + // Skip writing if the given port does not fit in the grid. + if (port >= grid.grid->get_writer().get_nof_ports()) { return; } - // Create the notification context. - uplane_rx_symbol_context notifier_context; - notifier_context.symbol = symbol; - notifier_context.slot = context.slot; - - // Notify. - notifier.on_new_uplink_symbol(notifier_context, grid->get_reader()); - - // Mark the symbol as sent. - rg_stats[symbol] = {}; + grid.grid->get_writer().put(port, symbol, start_re, re_iq_buffer); + re_written[port].fill(start_re, start_re + re_iq_buffer.size()); } -private: - /// Writes the given RE IQ buffer into the grid using the port, symbol and start RE. - void write_grid(unsigned port, unsigned symbol, unsigned start_re, span re_iq_buffer) + /// Tries to get a complete resource grid. A resource grid is considered completed when all the PRBs for all the ports + /// have been written. + expected try_getting_complete_resource_grid() const { - // Symbol is not valid for this grid or already been sent. - if (!rg_stats[symbol]) { - return; + if (!grid.grid) { + return default_error_t({}); } - // Skip writing if the given port does not fit in the grid. - if (port >= grid->get_writer().get_nof_ports()) { - return; + if (!have_all_prbs_been_written()) { + return default_error_t({}); } - grid->get_writer().put(port, symbol, start_re, re_iq_buffer); - rg_stats[symbol].re_written[port] += re_iq_buffer.size(); + return {grid}; } - /// Notify using the given notifier when all the REs for all the ports of the given symbol have been written. - void notify_symbol_when_complete(unsigned symbol, uplane_rx_symbol_notifier& notifier) +private: + /// Returns true when all the REs for the current symbol have been written. + bool have_all_prbs_been_written() const { - if (!rg_stats[symbol] || !rg_stats[symbol].have_all_prbs_been_written()) { - return; - } - - // Create the notification context. - uplane_rx_symbol_context notifier_context; - notifier_context.symbol = symbol; - notifier_context.slot = context.slot; - notifier_context.sector = context.sector; - - // Notify. - notifier.on_new_uplink_symbol(notifier_context, grid->get_reader()); - - // Mark the symbol as sent. - rg_stats[symbol] = {}; + return std::all_of( + re_written.begin(), re_written.end(), [](const auto& port_re_written) { return port_re_written.all(); }); } private: - resource_grid_context context; - resource_grid* grid = nullptr; - std::array rg_stats = {}; + unsigned symbol; + uplink_context_resource_grid_info grid; + static_vector, MAX_NOF_SUPPORTED_EAXC> re_written; }; /// Uplink context repository. -template class uplink_context_repository { /// System frame number maximum value in this repository. static constexpr unsigned SFN_MAX_VALUE = 1U << 8; - std::vector buffer; - const unsigned size; + std::vector> buffer; //: TODO: make this lock free mutable std::mutex mutex; - /// Returns the entry of the repository for the given slot. - T& entry(slot_point slot) + /// Returns the entry of the repository for the given slot and symbol. + uplink_context& entry(slot_point slot, unsigned symbol) { + srsran_assert(symbol < MAX_NSYMB_PER_SLOT, "Invalid symbol index={}", symbol); + slot_point entry_slot(slot.numerology(), slot.sfn() % SFN_MAX_VALUE, slot.slot_index()); - unsigned index = entry_slot.system_slot() % size; - return buffer[index]; + unsigned index = entry_slot.system_slot() % buffer.size(); + return buffer[index][symbol]; } - /// Returns the entry of the repository for the given slot. - const T& entry(slot_point slot) const + /// Returns the entry of the repository for the given slot and symbol. + const uplink_context& entry(slot_point slot, unsigned symbol) const { + srsran_assert(symbol < MAX_NSYMB_PER_SLOT, "Invalid symbol index={}", symbol); + slot_point entry_slot(slot.numerology(), slot.sfn() % SFN_MAX_VALUE, slot.slot_index()); - unsigned index = entry_slot.system_slot() % size; - return buffer[index]; + unsigned index = entry_slot.system_slot() % buffer.size(); + return buffer[index][symbol]; } public: - explicit uplink_context_repository(unsigned size_) : buffer(size_), size(size_) {} + explicit uplink_context_repository(unsigned size_) : buffer(size_) {} /// Adds the given entry to the repository at slot. - void add(slot_point slot, const T& new_entry) + void add(const resource_grid_context& context, resource_grid& grid) { std::lock_guard lock(mutex); - entry(slot) = new_entry; + for (unsigned symbol_id = 0, symbol_end = grid.get_reader().get_nof_symbols(); symbol_id != symbol_end; + ++symbol_id) { + entry(context.slot, symbol_id) = uplink_context(symbol_id, context, grid); + } } - /// Function to write the grid for the uplink slot context. - template - typename std::enable_if::value>::type - update_grid_and_notify(slot_point slot, - unsigned port, - unsigned symbol, - unsigned start_re, - span re_iq_buffer, - uplane_rx_symbol_notifier& notifier) + /// Writes to the grid at the given slot, port, symbol and start resource element the given IQ buffer. + void write_grid(slot_point slot, unsigned port, unsigned symbol, unsigned start_re, span re_iq_buffer) { std::lock_guard lock(mutex); - entry(slot).update_grid_and_notify(port, symbol, start_re, re_iq_buffer, notifier); + entry(slot, symbol).write_grid(port, start_re, re_iq_buffer); } - /// Function to write the uplink PRACH buffer. Returns True in case notifier was called, false otherwise. - template - typename std::enable_if::value, bool>::type - update_buffer_and_notify(slot_point slot, - unsigned port, - unsigned symbol, - span iq_buffer, - uplane_rx_symbol_notifier& notifier) + /// Returns the entry of the repository for the given slot and symbol. + uplink_context get(slot_point slot, unsigned symbol) const { std::lock_guard lock(mutex); - return entry(slot).update_buffer_and_notify(port, symbol, iq_buffer, notifier); + return entry(slot, symbol); } - /// Returns the entry of the repository for the given slot. - T get(slot_point slot) const + /// Clears the repository entry for the given slot and symbol. + void clear(slot_point slot, unsigned symbol) { std::lock_guard lock(mutex); - return entry(slot); + entry(slot, symbol) = {}; } }; diff --git a/lib/ofh/transmitter/ofh_data_flow_uplane_downlink_data_impl.cpp b/lib/ofh/transmitter/ofh_data_flow_uplane_downlink_data_impl.cpp index 201cef5239..95e8bae6c0 100644 --- a/lib/ofh/transmitter/ofh_data_flow_uplane_downlink_data_impl.cpp +++ b/lib/ofh/transmitter/ofh_data_flow_uplane_downlink_data_impl.cpp @@ -181,12 +181,13 @@ unsigned data_flow_uplane_downlink_data_impl::enqueue_section_type_1_message_sym span eth_buffer = span(buffer).first(ether_header_size.value() + bytes_written); eth_builder->build_vlan_frame(eth_buffer, vlan_params); - logger.debug("Creating User-Plane message for downlink at slot={}, symbol_id={}, prbs={}:{}, size={}", + logger.debug("Creating User-Plane message for downlink at slot={}, symbol_id={}, prbs={}:{}, size={}, eaxc={}", params.slot, params.symbol_id, params.start_prb, params.nof_prb, - eth_buffer.size()); + eth_buffer.size(), + eaxc); return eth_buffer.size(); } diff --git a/lib/ofh/transmitter/ofh_downlink_handler_broadcast_impl.cpp b/lib/ofh/transmitter/ofh_downlink_handler_broadcast_impl.cpp index 834da4ebe4..28afce3411 100644 --- a/lib/ofh/transmitter/ofh_downlink_handler_broadcast_impl.cpp +++ b/lib/ofh/transmitter/ofh_downlink_handler_broadcast_impl.cpp @@ -28,24 +28,36 @@ using namespace srsran; using namespace ofh; downlink_handler_broadcast_impl::downlink_handler_broadcast_impl( + srslog::basic_logger& logger_, cyclic_prefix cp_, const optional& tdd_config_, span eaxc_data_, std::unique_ptr data_flow_cplane_, - std::unique_ptr data_flow_uplane_) : + std::unique_ptr data_flow_uplane_, + std::unique_ptr window_checker_) : + logger(logger_), cp(cp_), tdd_config(tdd_config_), dl_eaxc(eaxc_data_.begin(), eaxc_data_.end()), data_flow_cplane(std::move(data_flow_cplane_)), - data_flow_uplane(std::move(data_flow_uplane_)) + data_flow_uplane(std::move(data_flow_uplane_)), + window_checker(std::move(window_checker_)) { srsran_assert(data_flow_cplane, "Invalid Control-Plane data flow"); srsran_assert(data_flow_uplane, "Invalid Use-Plane data flow"); + srsran_assert(window_checker, "Invalid transmission window checker"); } void downlink_handler_broadcast_impl::handle_dl_data(const resource_grid_context& context, const resource_grid_reader& grid) { + if (window_checker->is_late(context.slot)) { + logger.warning( + "Dropping downlink resource grid at slot={} and sector={} as it arrived late", context.slot, context.sector); + + return; + } + data_flow_cplane_type_1_context cplane_context; cplane_context.slot = context.slot; cplane_context.filter_type = filter_index_type::standard_channel_filter; diff --git a/lib/ofh/transmitter/ofh_downlink_handler_broadcast_impl.h b/lib/ofh/transmitter/ofh_downlink_handler_broadcast_impl.h index 3e81a7ecd9..0d1a4c0b87 100644 --- a/lib/ofh/transmitter/ofh_downlink_handler_broadcast_impl.h +++ b/lib/ofh/transmitter/ofh_downlink_handler_broadcast_impl.h @@ -24,6 +24,7 @@ #include "ofh_data_flow_cplane_scheduling_commands.h" #include "ofh_data_flow_uplane_downlink_data.h" +#include "ofh_tx_window_checker.h" #include "srsran/adt/span.h" #include "srsran/adt/static_vector.h" #include "srsran/ofh/ofh_constants.h" @@ -39,21 +40,25 @@ namespace ofh { class downlink_handler_broadcast_impl : public downlink_handler { public: - downlink_handler_broadcast_impl(cyclic_prefix cp_, + downlink_handler_broadcast_impl(srslog::basic_logger& logger_, + cyclic_prefix cp_, const optional& tdd_config_, span eaxc_data_, std::unique_ptr data_flow_cplane_, - std::unique_ptr data_flow_uplane_); + std::unique_ptr data_flow_uplane_, + std::unique_ptr window_checker_); // See interface for documentation. void handle_dl_data(const resource_grid_context& context, const resource_grid_reader& grid) override; private: + srslog::basic_logger& logger; const cyclic_prefix cp; const optional tdd_config; const static_vector dl_eaxc; std::unique_ptr data_flow_cplane; std::unique_ptr data_flow_uplane; + std::unique_ptr window_checker; }; } // namespace ofh diff --git a/lib/ofh/transmitter/ofh_uplink_request_handler_impl.cpp b/lib/ofh/transmitter/ofh_uplink_request_handler_impl.cpp index 9743f94d4c..6553e69120 100644 --- a/lib/ofh/transmitter/ofh_uplink_request_handler_impl.cpp +++ b/lib/ofh/transmitter/ofh_uplink_request_handler_impl.cpp @@ -79,12 +79,11 @@ void uplink_request_handler_impl::handle_prach_occasion(const prach_buffer_conte ? get_prach_preamble_long_info(context.format) : get_prach_preamble_short_info(context.format, to_ra_subcarrier_spacing(context.pusch_scs), true); - unsigned nof_prach_ports = std::min(size_t(buffer.get_max_nof_ports()), prach_eaxc.size()); - ul_prach_context repo_context(context, buffer, nof_prach_ports); + unsigned nof_prach_ports = std::min(size_t(buffer.get_max_nof_ports()), prach_eaxc.size()); // Store the context in the repository, use correct slot index for long format accounting for PRACH duration. if (is_short_preamble(context.format)) { - ul_prach_repo.add(context.slot, repo_context); + ul_prach_repo.add(context, buffer, nof_prach_ports); } else { static constexpr unsigned nof_symbols_per_slot = get_nsymb_per_slot(cyclic_prefix::NORMAL); @@ -100,7 +99,7 @@ void uplink_request_handler_impl::handle_prach_occasion(const prach_buffer_conte // Subtract one to account for the current slot. slot_point slot = context.slot + (prach_length_slots - 1); - ul_prach_repo.add(slot, repo_context); + ul_prach_repo.add(context, buffer, nof_prach_ports, slot); } if (!is_prach_cp_enabled) { @@ -127,7 +126,7 @@ void uplink_request_handler_impl::handle_prach_occasion(const prach_buffer_conte data_flow_cplane_scheduling_prach_context cp_prach_context = {}; cp_prach_context.slot = context.slot; - cp_prach_context.nof_repetitions = repo_context.get_prach_nof_symbols(); + cp_prach_context.nof_repetitions = get_preamble_duration(context.format); cp_prach_context.start_symbol = prach_start_symbol; cp_prach_context.prach_scs = preamble_info.scs; cp_prach_context.scs = context.pusch_scs; @@ -147,8 +146,7 @@ void uplink_request_handler_impl::handle_prach_occasion(const prach_buffer_conte void uplink_request_handler_impl::handle_new_uplink_slot(const resource_grid_context& context, resource_grid& grid) { // Store the context in the repository. - ul_slot_context repo_context(context, grid); - ul_slot_repo.add(context.slot, repo_context); + ul_slot_repo.add(context, grid); data_flow_cplane_type_1_context df_context; df_context.slot = context.slot; diff --git a/lib/ofh/transmitter/ofh_uplink_request_handler_impl.h b/lib/ofh/transmitter/ofh_uplink_request_handler_impl.h index ec2ac3b0e7..ebcc1accdf 100644 --- a/lib/ofh/transmitter/ofh_uplink_request_handler_impl.h +++ b/lib/ofh/transmitter/ofh_uplink_request_handler_impl.h @@ -22,6 +22,7 @@ #pragma once +#include "../support/prach_context_repository.h" #include "../support/uplink_context_repository.h" #include "ofh_data_flow_cplane_scheduling_commands.h" #include "srsran/adt/optional.h" @@ -48,9 +49,9 @@ struct uplink_request_handler_impl_config { /// Uplink request handler implmentation dependencies. struct uplink_request_handler_impl_dependencies { /// Uplink slot context repository. - std::shared_ptr> ul_slot_repo; + std::shared_ptr ul_slot_repo; /// Uplink PRACH context repository. - std::shared_ptr> ul_prach_repo; + std::shared_ptr ul_prach_repo; /// Data flow for Control-Plane scheduling commands. std::unique_ptr data_flow; }; @@ -69,16 +70,16 @@ class uplink_request_handler_impl : public uplink_request_handler void handle_new_uplink_slot(const resource_grid_context& context, resource_grid& grid) override; private: - bool is_prach_cp_enabled; - const cyclic_prefix cp; - const optional tdd_config; - const static_vector prach_eaxc; - const static_vector ul_eaxc; - std::shared_ptr> ul_slot_repo_ptr; - std::shared_ptr> ul_prach_repo_ptr; - uplink_context_repository& ul_slot_repo; - uplink_context_repository& ul_prach_repo; - std::unique_ptr data_flow; + bool is_prach_cp_enabled; + const cyclic_prefix cp; + const optional tdd_config; + const static_vector prach_eaxc; + const static_vector ul_eaxc; + std::shared_ptr ul_slot_repo_ptr; + std::shared_ptr ul_prach_repo_ptr; + uplink_context_repository& ul_slot_repo; + prach_context_repository& ul_prach_repo; + std::unique_ptr data_flow; }; } // namespace ofh diff --git a/lib/pcap/dlt_pcap_impl.cpp b/lib/pcap/dlt_pcap_impl.cpp index c2281e3d53..be173d9d62 100644 --- a/lib/pcap/dlt_pcap_impl.cpp +++ b/lib/pcap/dlt_pcap_impl.cpp @@ -28,8 +28,11 @@ namespace srsran { constexpr uint16_t pcap_dlt_max_pdu_len = 9000; -dlt_pcap_impl::dlt_pcap_impl(unsigned dlt_, const std::string& layer_name_) : - dlt(dlt_), layer_name(layer_name_), worker(layer_name_ + "-PCAP", 1024) +dlt_pcap_impl::dlt_pcap_impl(unsigned dlt_, const std::string& layer_name_, os_sched_affinity_bitmask cpu_mask_) : + dlt(dlt_), + layer_name(layer_name_), + cpu_mask(cpu_mask_), + worker(layer_name_ + "-PCAP", 1024, os_thread_realtime_priority::no_realtime(), cpu_mask) { tmp_mem.resize(pcap_dlt_max_pdu_len); } diff --git a/lib/pcap/dlt_pcap_impl.h b/lib/pcap/dlt_pcap_impl.h index ba91de6472..c39fdd7a3d 100644 --- a/lib/pcap/dlt_pcap_impl.h +++ b/lib/pcap/dlt_pcap_impl.h @@ -32,7 +32,7 @@ namespace srsran { class dlt_pcap_impl final : public dlt_pcap { public: - dlt_pcap_impl(unsigned dlt_, const std::string& layer_name_); + dlt_pcap_impl(unsigned dlt_, const std::string& layer_name_, os_sched_affinity_bitmask cpu_mask = {}); ~dlt_pcap_impl() override; dlt_pcap_impl(const dlt_pcap_impl& other) = delete; dlt_pcap_impl& operator=(const dlt_pcap_impl& other) = delete; @@ -46,13 +46,14 @@ class dlt_pcap_impl final : public dlt_pcap void push_pdu(srsran::const_span pdu) override; private: - unsigned dlt; - std::string layer_name; - void write_pdu(srsran::byte_buffer buf); - task_worker worker; - std::vector tmp_mem; - pcap_file_base writter; - std::atomic is_open{false}; + unsigned dlt; + std::string layer_name; + void write_pdu(srsran::byte_buffer buf); + os_sched_affinity_bitmask cpu_mask; + task_worker worker; + std::vector tmp_mem; + pcap_file_base writter; + std::atomic is_open{false}; }; } // namespace srsran diff --git a/lib/pcap/mac_pcap_impl.cpp b/lib/pcap/mac_pcap_impl.cpp index 1454a20d07..3ceb6d4b8c 100644 --- a/lib/pcap/mac_pcap_impl.cpp +++ b/lib/pcap/mac_pcap_impl.cpp @@ -29,12 +29,19 @@ namespace srsran { constexpr uint16_t UDP_DLT = 149; +constexpr uint16_t MAC_DLT = 157; -constexpr uint16_t pcap_mac_max_pdu_len = 32768; +constexpr uint32_t pcap_mac_max_pdu_len = 131072; int nr_pcap_pack_mac_context_to_buffer(const mac_nr_context_info& context, uint8_t* buffer, unsigned int length); -mac_pcap_impl::mac_pcap_impl() : worker("MAC-PCAP", 1024) +mac_pcap_impl::mac_pcap_impl() : worker("MAC-PCAP", 1024, os_thread_realtime_priority::no_realtime(), cpu_mask) +{ + tmp_mem.resize(pcap_mac_max_pdu_len); +} + +mac_pcap_impl::mac_pcap_impl(const srsran::os_sched_affinity_bitmask& mask) : + cpu_mask(mask), worker("MAC-PCAP", 1024, os_thread_realtime_priority::no_realtime(), cpu_mask) { tmp_mem.resize(pcap_mac_max_pdu_len); } @@ -44,11 +51,16 @@ mac_pcap_impl::~mac_pcap_impl() close(); } -void mac_pcap_impl::open(const std::string& filename_) +void mac_pcap_impl::open(const std::string& filename_, mac_pcap_type type_) { - is_open = true; + is_open = true; + type = type_; + uint16_t dlt = UDP_DLT; + if (type == mac_pcap_type::dlt) { + dlt = MAC_DLT; + } // Capture filename_ by copy to prevent it goes out-of-scope when the lambda is executed later - auto fn = [this, filename_]() { writter.dlt_pcap_open(UDP_DLT, filename_); }; + auto fn = [this, dlt, filename_]() { writter.dlt_pcap_open(dlt, filename_); }; worker.push_task_blocking(fn); } @@ -106,32 +118,29 @@ void mac_pcap_impl::write_pdu(const mac_nr_context_info& context, srsran::byte_b struct udphdr* udp_header; int offset = 0; - /* Can't write if file wasn't successfully opened */ - // TODO - - // Add dummy UDP header, start with src and dest port - udp_header = (struct udphdr*)context_header; - udp_header->dest = htons(0xdead); - offset += 2; - udp_header->source = htons(0xbeef); - offset += 2; - // length to be filled later - udp_header->len = 0x0000; - offset += 2; - // dummy CRC - udp_header->check = 0x0000; - offset += 2; - - // Start magic string - memcpy(&context_header[offset], MAC_NR_START_STRING, strlen(MAC_NR_START_STRING)); - offset += strlen(MAC_NR_START_STRING); + if (type == mac_pcap_type::udp) { + // Add dummy UDP header, start with src and dest port + udp_header = (struct udphdr*)context_header; + udp_header->dest = htons(0xdead); + offset += 2; + udp_header->source = htons(0xbeef); + offset += 2; + // length to be filled later + udp_header->len = 0x0000; + offset += 2; + // dummy CRC + udp_header->check = 0x0000; + offset += 2; + + // Start magic string + memcpy(&context_header[offset], MAC_NR_START_STRING, strlen(MAC_NR_START_STRING)); + offset += strlen(MAC_NR_START_STRING); + } offset += nr_pcap_pack_mac_context_to_buffer(context, &context_header[offset], PCAP_CONTEXT_HEADER_MAX); - udp_header->len = htons(offset + length); - - if (offset != 31) { - printf("ERROR Does not match offset %d != 31\n", offset); + if (type == mac_pcap_type::udp) { + udp_header->len = htons(offset + length); } // Write header diff --git a/lib/pcap/mac_pcap_impl.h b/lib/pcap/mac_pcap_impl.h index 2182a20abb..cedb3be28f 100644 --- a/lib/pcap/mac_pcap_impl.h +++ b/lib/pcap/mac_pcap_impl.h @@ -29,8 +29,6 @@ namespace srsran { -constexpr uint16_t MAC_PCAP_MAX_PDU_LEN = 32768; - // PCAP tags constexpr const char* MAC_NR_START_STRING = "mac-nr"; constexpr uint8_t MAC_NR_PAYLOAD_TAG = 0x01; @@ -44,23 +42,26 @@ class mac_pcap_impl final : public mac_pcap { public: mac_pcap_impl(); + explicit mac_pcap_impl(const os_sched_affinity_bitmask& mask); ~mac_pcap_impl() override; mac_pcap_impl(const mac_pcap_impl& other) = delete; mac_pcap_impl& operator=(const mac_pcap_impl& other) = delete; mac_pcap_impl(mac_pcap_impl&& other) = delete; mac_pcap_impl& operator=(mac_pcap_impl&& other) = delete; - void open(const std::string& filename_) override; + void open(const std::string& filename_, mac_pcap_type type) override; void close() override; bool is_write_enabled() override; void push_pdu(mac_nr_context_info context, const_span pdu) override; void push_pdu(mac_nr_context_info context, byte_buffer pdu) override; private: - void write_pdu(const mac_nr_context_info& context, byte_buffer pdu); - std::vector tmp_mem; - task_worker worker; - pcap_file_base writter; - std::atomic is_open{false}; + void write_pdu(const mac_nr_context_info& context, byte_buffer pdu); + mac_pcap_type type; + std::vector tmp_mem; + os_sched_affinity_bitmask cpu_mask; + task_worker worker; + pcap_file_base writter; + std::atomic is_open{false}; }; } // namespace srsran diff --git a/lib/pdcp/pdcp_entity_tx.cpp b/lib/pdcp/pdcp_entity_tx.cpp index c379c94b00..31990a4259 100644 --- a/lib/pdcp/pdcp_entity_tx.cpp +++ b/lib/pdcp/pdcp_entity_tx.cpp @@ -41,10 +41,14 @@ void pdcp_entity_tx::handle_sdu(byte_buffer sdu) logger.log_error("Invalid state, tx_trans is larger than tx_next. {}", st); return; } - if ((st.tx_next - st.tx_trans) >= 1024) { + if ((st.tx_next - st.tx_trans) >= 4096) { logger.log_info("Dropping SDU to avoid overloading RLC queue. {}", st); return; } + if ((st.tx_next - st.tx_trans) >= (window_size - 1)) { + logger.log_info("Dropping SDU to avoid going over the TX window size. {}", st); + return; + } metrics_add_sdus(1, sdu.length()); logger.log_debug(sdu.begin(), sdu.end(), "TX SDU. sdu_len={}", sdu.length()); @@ -78,10 +82,21 @@ void pdcp_entity_tx::handle_sdu(byte_buffer sdu) // Pack header byte_buffer header_buf = {}; - write_data_pdu_header(header_buf, hdr); + if (not write_data_pdu_header(header_buf, hdr)) { + logger.log_error("Could not append PDU header, dropping SDU and notifying RRC. count={}", st.tx_next); + upper_cn.on_protocol_failure(); + return; + } // Apply ciphering and integrity protection - byte_buffer protected_buf = apply_ciphering_and_integrity_protection(std::move(header_buf), sdu, st.tx_next); + expected exp_buf = apply_ciphering_and_integrity_protection(std::move(header_buf), sdu, st.tx_next); + if (exp_buf.is_error()) { + logger.log_error("Could not apply ciphering and integrity protection, dropping SDU and notifying RRC. count={}", + st.tx_next); + upper_cn.on_protocol_failure(); + return; + } + byte_buffer protected_buf = std::move(exp_buf.value()); // Start discard timer. If using RLC AM, we store // the PDU to use later in the data recovery procedure. @@ -247,7 +262,7 @@ void pdcp_entity_tx::handle_status_report(byte_buffer_chain status) /* * Ciphering and Integrity Protection Helpers */ -byte_buffer +expected pdcp_entity_tx::apply_ciphering_and_integrity_protection(byte_buffer hdr, const byte_buffer& sdu, uint32_t count) { // TS 38.323, section 5.9: Integrity protection @@ -256,8 +271,12 @@ pdcp_entity_tx::apply_ciphering_and_integrity_protection(byte_buffer hdr, const security::sec_mac mac = {}; if (integrity_enabled == security::integrity_enabled::on) { byte_buffer buf = {}; - buf.append(hdr); - buf.append(sdu); + if (not buf.append(hdr)) { + return default_error_t{}; + } + if (not buf.append(sdu)) { + return default_error_t{}; + } integrity_generate(mac, buf, count); } @@ -268,24 +287,36 @@ pdcp_entity_tx::apply_ciphering_and_integrity_protection(byte_buffer hdr, const byte_buffer ct; if (ciphering_enabled == security::ciphering_enabled::on) { byte_buffer buf = {}; - buf.append(sdu); + if (not buf.append(sdu)) { + return default_error_t{}; + } // Append MAC-I if (is_srb() || (is_drb() && (integrity_enabled == security::integrity_enabled::on))) { - buf.append(mac); + if (not buf.append(mac)) { + return default_error_t{}; + } } ct = cipher_encrypt(buf, count); } else { - ct.append(sdu); + if (not ct.append(sdu)) { + return default_error_t{}; + } // Append MAC-I if (is_srb() || (is_drb() && (integrity_enabled == security::integrity_enabled::on))) { - ct.append(mac); + if (not ct.append(mac)) { + return default_error_t{}; + } } } // Construct the protected buffer byte_buffer protected_buf; - protected_buf.append(hdr); - protected_buf.append(ct); + if (not protected_buf.append(hdr)) { + return default_error_t{}; + } + if (not protected_buf.append(ct)) { + return default_error_t{}; + } return protected_buf; } @@ -392,14 +423,27 @@ void pdcp_entity_tx::retransmit_all_pdus() // Pack header byte_buffer header_buf = {}; - write_data_pdu_header(header_buf, hdr); + if (not write_data_pdu_header(header_buf, hdr)) { + logger.log_error("Could not append PDU header, dropping SDU and notifying RRC. count={}", st.tx_next); + upper_cn.on_protocol_failure(); + return; + } // Perform header compression if required // (TODO) // Perform integrity protection and ciphering - byte_buffer protected_buf = + expected exp_buf = apply_ciphering_and_integrity_protection(std::move(header_buf), info.second.sdu, info.second.count); + if (exp_buf.is_error()) { + logger.log_error("Could not apply ciphering and integrity protection during retransmissions, dropping SDU and " + "notifying RRC. count={}", + info.second.count); + upper_cn.on_protocol_failure(); + return; + } + + byte_buffer protected_buf = std::move(exp_buf.value()); write_data_pdu_to_lower_layers(info.first, std::move(protected_buf)); } } @@ -487,7 +531,7 @@ uint32_t pdcp_entity_tx::notification_count_estimation(uint32_t notification_sn) /* * PDU Helpers */ -void pdcp_entity_tx::write_data_pdu_header(byte_buffer& buf, const pdcp_data_pdu_header& hdr) const +bool pdcp_entity_tx::write_data_pdu_header(byte_buffer& buf, const pdcp_data_pdu_header& hdr) const { // Sanity check: 18-bit SN not allowed for SRBs srsran_assert( @@ -497,25 +541,39 @@ void pdcp_entity_tx::write_data_pdu_header(byte_buffer& buf, const pdcp_data_pdu // Set D/C if required if (is_drb()) { - hdr_writer.append(0x80); // D/C bit field (1). + // D/C bit field (1). + if (not hdr_writer.append(0x80)) { + return false; + } } else { - hdr_writer.append(0x00); // No D/C bit field. + // No D/C bit field. + if (not hdr_writer.append(0x00)) { + return false; + } } // Add SN switch (cfg.sn_size) { case pdcp_sn_size::size12bits: hdr_writer.back() |= (hdr.sn & 0x00000f00U) >> 8U; - hdr_writer.append((hdr.sn & 0x000000ffU)); + if (not hdr_writer.append((hdr.sn & 0x000000ffU))) { + return false; + } break; case pdcp_sn_size::size18bits: hdr_writer.back() |= (hdr.sn & 0x00030000U) >> 16U; - hdr_writer.append((hdr.sn & 0x0000ff00U) >> 8U); - hdr_writer.append((hdr.sn & 0x000000ffU)); + if (not hdr_writer.append((hdr.sn & 0x0000ff00U) >> 8U)) { + return false; + } + if (not hdr_writer.append((hdr.sn & 0x000000ffU))) { + return false; + } break; default: logger.log_error("Invalid sn_size={}", cfg.sn_size); + return false; } + return true; } /* @@ -558,6 +616,12 @@ void pdcp_entity_tx::discard_callback::operator()(timer_id_t timer_id) // Add discard to metrics parent->metrics_add_discard_timouts(1); + if (parent->st.tx_trans < discard_count) { + // We are discarding a PDU, it can no longer be in the RLC SDU queue. + // Advance TX_TRANS accordingly + parent->st.tx_trans = discard_count + 1; + } + // Remove timer from map // NOTE: this will delete the callback. It *must* be the last instruction. parent->discard_timers_map.erase(discard_count); diff --git a/lib/pdcp/pdcp_entity_tx.h b/lib/pdcp/pdcp_entity_tx.h index df999f4fe0..5ee78bdf6b 100644 --- a/lib/pdcp/pdcp_entity_tx.h +++ b/lib/pdcp/pdcp_entity_tx.h @@ -29,6 +29,7 @@ #include "pdcp_tx_metrics_impl.h" #include "srsran/adt/byte_buffer.h" #include "srsran/adt/byte_buffer_chain.h" +#include "srsran/adt/expected.h" #include "srsran/pdcp/pdcp_config.h" #include "srsran/pdcp/pdcp_tx.h" #include "srsran/security/security.h" @@ -123,7 +124,7 @@ class pdcp_entity_tx final : public pdcp_entity_tx_rx_base, /// \brief Writes the header of a PDCP data PDU according to the content of the associated object /// \param[out] buf Reference to a byte_buffer that is appended by the header bytes /// \param[in] hdr Reference to a pdcp_data_pdu_header that represents the header content - void write_data_pdu_header(byte_buffer& buf, const pdcp_data_pdu_header& hdr) const; + SRSRAN_NODISCARD bool write_data_pdu_header(byte_buffer& buf, const pdcp_data_pdu_header& hdr) const; /* * Testing helpers @@ -217,7 +218,8 @@ class pdcp_entity_tx final : public pdcp_entity_tx_rx_base, void write_control_pdu_to_lower_layers(byte_buffer buf); /// Apply ciphering and integrity protection to the payload - byte_buffer apply_ciphering_and_integrity_protection(byte_buffer hdr, const byte_buffer& sdu, uint32_t count); + expected + apply_ciphering_and_integrity_protection(byte_buffer hdr, const byte_buffer& sdu, uint32_t count); void integrity_generate(security::sec_mac& mac, byte_buffer_view buf, uint32_t count); byte_buffer cipher_encrypt(byte_buffer_view buf, uint32_t count); diff --git a/lib/phy/support/resource_grid_impl.cpp b/lib/phy/support/resource_grid_impl.cpp index 315f6850e7..88677581c4 100644 --- a/lib/phy/support/resource_grid_impl.cpp +++ b/lib/phy/support/resource_grid_impl.cpp @@ -30,33 +30,30 @@ resource_grid_impl::resource_grid_impl(unsigned nof_por unsigned nof_symb_, unsigned nof_subc_, std::unique_ptr precoder_) : - empty(nof_ports_), nof_ports(nof_ports_), nof_symb(nof_symb_), nof_subc(nof_subc_), writer(rg_buffer, empty), reader(rg_buffer, empty), - mapper(nof_ports_, nof_symb_, nof_subc_, writer, std::move(precoder_)) + mapper(nof_ports_, nof_subc_, writer, std::move(precoder_)) { // Reserve memory for the internal buffer. rg_buffer.reserve({nof_subc, nof_symb, nof_ports}); // Set all the resource elements to zero. - for (unsigned port = 0; port != nof_ports; ++port) { - srsvec::zero(rg_buffer.get_view(resource_grid_dimensions::port)>({port})); - empty[port] = true; - } + srsvec::zero(rg_buffer.get_data()); + empty = (1U << nof_ports) - 1; } void resource_grid_impl::set_all_zero() { // For each non-empty port, set the underlying resource elements to zero. for (unsigned port = 0; port != nof_ports; ++port) { - if (!empty[port]) { + if (!reader.is_port_empty(port)) { srsvec::zero(rg_buffer.get_view(resource_grid_dimensions::port)>({port})); - empty[port] = true; } } + empty = (1U << nof_ports) - 1; } resource_grid_writer& resource_grid_impl::get_writer() diff --git a/lib/phy/support/resource_grid_impl.h b/lib/phy/support/resource_grid_impl.h index 8697417e38..68a7ecf6d6 100644 --- a/lib/phy/support/resource_grid_impl.h +++ b/lib/phy/support/resource_grid_impl.h @@ -38,10 +38,10 @@ class resource_grid_mapper; class resource_grid_impl : public resource_grid { private: - static_vector empty; - unsigned nof_ports; - unsigned nof_symb; - unsigned nof_subc; + std::atomic empty = {}; + unsigned nof_ports; + unsigned nof_symb; + unsigned nof_subc; /// \brief Stores the resource grid data. /// diff --git a/lib/phy/support/resource_grid_mapper_impl.cpp b/lib/phy/support/resource_grid_mapper_impl.cpp index bea45ae330..88c8051127 100644 --- a/lib/phy/support/resource_grid_mapper_impl.cpp +++ b/lib/phy/support/resource_grid_mapper_impl.cpp @@ -23,30 +23,32 @@ #include "resource_grid_mapper_impl.h" #include "srsran/phy/support/precoding_configuration.h" #include "srsran/phy/support/re_pattern.h" -#include "srsran/phy/support/re_pattern_formatters.h" #include "srsran/srsvec/sc_prod.h" using namespace srsran; resource_grid_mapper_impl::resource_grid_mapper_impl(unsigned nof_ports_, - unsigned nof_symb_, unsigned nof_subc_, resource_grid_writer& writer_, std::unique_ptr precoder_) : - nof_ports(nof_ports_), - nof_symb(nof_symb_), - nof_subc(nof_subc_), - writer(writer_), - precoder(std::move(precoder_)), - layer_mapping_buffer(nof_ports, nof_subc * nof_symb), - precoding_buffer(nof_ports, nof_subc * nof_symb) + nof_ports(nof_ports_), nof_subc(nof_subc_), writer(writer_), precoder(std::move(precoder_)) { + srsran_assert(nof_ports <= max_nof_ports, + "The number of ports (i.e., {}) exceeds the maximum number of ports (i.e., {}).", + nof_ports, + static_cast(max_nof_ports)); + srsran_assert(nof_subc <= max_nof_subcarriers, + "The number of subcarriers (i.e., {}) exceeds the maximum number of subcarriers (i.e., {}).", + nof_subc, + static_cast(max_nof_subcarriers)); } void resource_grid_mapper_impl::map(const re_buffer_reader& input, const re_pattern_list& pattern, const re_pattern_list& reserved, const precoding_configuration& precoding) { + static_re_buffer precoding_buffer; + unsigned nof_layers = precoding.get_nof_layers(); srsran_assert(input.get_nof_slices() == precoding.get_nof_layers(), @@ -67,7 +69,7 @@ void resource_grid_mapper_impl::map(const re_buffer_reader& input, unsigned i_re_buffer = 0; for (unsigned i_symbol = 0; i_symbol != MAX_NSYMB_PER_SLOT; ++i_symbol) { // Get the symbol RE mask. - bounded_bitset symbol_re_mask(nof_subc); + bounded_bitset symbol_re_mask(nof_subc); pattern.get_inclusion_mask(symbol_re_mask, i_symbol); reserved.get_exclusion_mask(symbol_re_mask, i_symbol); @@ -112,7 +114,7 @@ void resource_grid_mapper_impl::map(const re_buffer_reader& input, unsigned nof_subc_prg = std::min(prg_size, static_cast(symbol_re_mask.size()) - i_subc); // Mask for the RE belonging to the current PRG. - bounded_bitset prg_re_mask = symbol_re_mask.slice(i_subc, i_subc + nof_subc_prg); + bounded_bitset prg_re_mask = symbol_re_mask.slice(i_subc, i_subc + nof_subc_prg); // Number of allocated RE for the current PRG. unsigned nof_re_prg = prg_re_mask.count(); @@ -166,6 +168,8 @@ void resource_grid_mapper_impl::map(symbol_buffer& buffer, const re_pattern_list& reserved, const precoding_configuration& precoding) { + static_re_buffer precoding_buffer; + unsigned max_block_size = buffer.get_max_block_size(); // The number of layers is equal to the number of ports. @@ -188,7 +192,7 @@ void resource_grid_mapper_impl::map(symbol_buffer& buffer, for (unsigned i_symbol = 0; i_symbol != MAX_NSYMB_PER_SLOT; ++i_symbol) { // Get the symbol RE mask. - bounded_bitset symbol_re_mask(MAX_RB * NRE); + bounded_bitset symbol_re_mask(max_nof_subcarriers); pattern.get_inclusion_mask(symbol_re_mask, i_symbol); reserved.get_exclusion_mask(symbol_re_mask, i_symbol); @@ -211,7 +215,7 @@ void resource_grid_mapper_impl::map(symbol_buffer& buffer, unsigned nof_subc_prg = std::min(prg_size, static_cast(i_highest_subc + 1) - i_subc); // Mask for the RE belonging to the current PRG. - bounded_bitset prg_re_mask = symbol_re_mask.slice(i_subc, i_subc + nof_subc_prg); + bounded_bitset prg_re_mask = symbol_re_mask.slice(i_subc, i_subc + nof_subc_prg); // Skip PRG if no RE is selected. if (prg_re_mask.none()) { @@ -232,7 +236,7 @@ void resource_grid_mapper_impl::map(symbol_buffer& buffer, unsigned nof_subc_block = std::min(nof_subc_pending, max_nof_subc_block); // Get the allocation mask for the block. - bounded_bitset block_mask = prg_re_mask.slice(subc_offset, subc_offset + nof_subc_block); + bounded_bitset block_mask = prg_re_mask.slice(subc_offset, subc_offset + nof_subc_block); // Count the number of resource elements to map in the block. unsigned nof_re_block = block_mask.count(); diff --git a/lib/phy/support/resource_grid_mapper_impl.h b/lib/phy/support/resource_grid_mapper_impl.h index 874b3cd21b..6c10f4309b 100644 --- a/lib/phy/support/resource_grid_mapper_impl.h +++ b/lib/phy/support/resource_grid_mapper_impl.h @@ -33,7 +33,6 @@ class resource_grid_mapper_impl : public resource_grid_mapper { public: resource_grid_mapper_impl(unsigned nof_ports_, - unsigned nof_symb_, unsigned nof_subc_, resource_grid_writer& writer_, std::unique_ptr precoder_); @@ -55,12 +54,13 @@ class resource_grid_mapper_impl : public resource_grid_mapper const precoding_configuration& precoding) override; private: - /// Maximum number of symbols that can be layer mapped, precoded and mapped onto the resource grid at once. - static constexpr unsigned MAX_NOF_SYMBOLS = 512; + /// Maximum number of subcarriers that can be accomodated in an OFDM symbol. + static constexpr unsigned max_nof_subcarriers = MAX_RB * NRE; + /// Maximum number of ports to map in a mapping call. + static constexpr unsigned max_nof_ports = 4U; /// Resource grid dimensions. unsigned nof_ports; - unsigned nof_symb; unsigned nof_subc; /// Resource grid writer. @@ -68,12 +68,6 @@ class resource_grid_mapper_impl : public resource_grid_mapper /// Channel precoder. std::unique_ptr precoder; - - /// Temporal layer mapping output buffer, used to store data between layer mapping and precoding. - dynamic_re_buffer layer_mapping_buffer; - - /// Temporal output buffer, used to store the Resource Elements after precoding. - dynamic_re_buffer precoding_buffer; }; } // namespace srsran diff --git a/lib/phy/support/resource_grid_reader_impl.cpp b/lib/phy/support/resource_grid_reader_impl.cpp index 9e4dea0712..0548cdf7eb 100644 --- a/lib/phy/support/resource_grid_reader_impl.cpp +++ b/lib/phy/support/resource_grid_reader_impl.cpp @@ -42,8 +42,8 @@ unsigned resource_grid_reader_impl::get_nof_symbols() const bool resource_grid_reader_impl::is_empty(unsigned port) const { - srsran_assert(port < empty.size(), "Port index {} is out of range (max {})", port, empty.size()); - return empty[port]; + srsran_assert(port < get_nof_ports(), "Port index {} is out of range (max {})", port, get_nof_ports()); + return is_port_empty(port); } span resource_grid_reader_impl::get(span symbols, @@ -107,15 +107,27 @@ span resource_grid_reader_impl::get(span sy get_nof_ports()); // Get view of the OFDM symbol subcarriers. - span symb = data.get_view({l, port}); + span symb = data.get_view({l, port}).subspan(k_init, mask.size()); srsran_assert(mask.count() <= symbols.size(), "The number ones in mask {} exceeds the number of symbols {}.", mask.count(), symbols.size()); - mask.for_each(0, mask.size(), [&](unsigned i_subc) { - symbols.front() = symb[i_subc + k_init]; + unsigned mask_count = mask.count(); + srsran_assert(mask_count <= symbols.size(), + "The number of active subcarriers (i.e., {}) exceeds the number of symbols (i.e., {}).", + mask_count, + symbols.size()); + + // Do a straight copy if the elements of the mask are all contiguous. + if (mask_count and mask.is_contiguous()) { + srsvec::copy(symbols.first(mask_count), symb.subspan(mask.find_lowest(), mask_count)); + return symbols.last(symbols.size() - mask_count); + } + + mask.for_each(0, mask.size(), [symb, &symbols](unsigned i_subc) { + symbols.front() = symb[i_subc]; symbols = symbols.last(symbols.size() - 1); }); diff --git a/lib/phy/support/resource_grid_reader_impl.h b/lib/phy/support/resource_grid_reader_impl.h index 41a02a734c..806f92b567 100644 --- a/lib/phy/support/resource_grid_reader_impl.h +++ b/lib/phy/support/resource_grid_reader_impl.h @@ -33,7 +33,9 @@ class resource_grid_reader_impl : public resource_grid_reader using storage_type = tensor(resource_grid_dimensions::all), cf_t, resource_grid_dimensions>; /// Constructs a resource grid reader implementation from a tensor. - resource_grid_reader_impl(const storage_type& data_, span empty_) : data(data_), empty(empty_) {} + resource_grid_reader_impl(const storage_type& data_, const std::atomic& empty_) : data(data_), empty(empty_) + { + } // See interface for documentation. unsigned get_nof_ports() const override; @@ -63,9 +65,12 @@ class resource_grid_reader_impl : public resource_grid_reader // See interface for documentation. span get_view(unsigned port, unsigned l) const override; + /// Checks if a port is empty. + bool is_port_empty(unsigned i_port) const { return (empty & (1U << i_port)) != 0; } + private: - const storage_type& data; - span empty; + const storage_type& data; + const std::atomic& empty; }; } // namespace srsran diff --git a/lib/phy/support/resource_grid_writer_impl.cpp b/lib/phy/support/resource_grid_writer_impl.cpp index b3353f4813..4a7924d240 100644 --- a/lib/phy/support/resource_grid_writer_impl.cpp +++ b/lib/phy/support/resource_grid_writer_impl.cpp @@ -69,7 +69,7 @@ void resource_grid_writer_impl::put(unsigned port, // Write into the desired resource element. rg_symbol[coordinate.subcarrier] = symbols[count++]; } - empty[port] = false; + clear_empty(port); } span resource_grid_writer_impl::put(unsigned port, @@ -99,7 +99,7 @@ span resource_grid_writer_impl::put(unsigned port, symbol_buffer = symbol_buffer.last(symbol_buffer.size() - 1); } } - empty[port] = false; + clear_empty(port); // Update symbol buffer return symbol_buffer; @@ -122,7 +122,7 @@ span resource_grid_writer_impl::put(unsigned // Get view of the OFDM symbol subcarriers. span symb = data.get_view({l, port}).subspan(k_init, mask.size()); - empty[port] = false; + clear_empty(port); unsigned mask_count = mask.count(); srsran_assert(mask_count <= symbols.size(), @@ -167,5 +167,5 @@ void resource_grid_writer_impl::put(unsigned port, unsigned l, unsigned k_init, // Copy resource elements. srsvec::copy(rg_symbol.subspan(k_init, symbols.size()), symbols); - empty[port] = false; + clear_empty(port); } diff --git a/lib/phy/support/resource_grid_writer_impl.h b/lib/phy/support/resource_grid_writer_impl.h index a20fa3e334..504e4eba57 100644 --- a/lib/phy/support/resource_grid_writer_impl.h +++ b/lib/phy/support/resource_grid_writer_impl.h @@ -33,7 +33,7 @@ class resource_grid_writer_impl : public resource_grid_writer using storage_type = tensor(resource_grid_dimensions::all), cf_t, resource_grid_dimensions>; /// Constructs a resource grid writer implementation from a tensor. - resource_grid_writer_impl(storage_type& data_, span empty_) : data(data_), empty(empty_) {} + resource_grid_writer_impl(storage_type& data_, std::atomic& empty_) : data(data_), empty(empty_) {} // See interface for documentation. unsigned get_nof_ports() const override; @@ -61,9 +61,12 @@ class resource_grid_writer_impl : public resource_grid_writer // See interface for documentation. void put(unsigned port, unsigned l, unsigned k_init, span symbols) override; + /// Helper function to mark port as not empty. + inline void clear_empty(unsigned i_port) { empty &= ~(1U << i_port); } + private: - storage_type& data; - span empty; + storage_type& data; + std::atomic& empty; }; } // namespace srsran diff --git a/lib/phy/support/support_factories.cpp b/lib/phy/support/support_factories.cpp index 4bcd4db432..fca49f91ea 100644 --- a/lib/phy/support/support_factories.cpp +++ b/lib/phy/support/support_factories.cpp @@ -182,11 +182,9 @@ class channel_precoder_dummy : public channel_precoder } }; -std::unique_ptr srsran::create_resource_grid_mapper(unsigned nof_ports, - unsigned nof_symbols, - unsigned nof_subc, - srsran::resource_grid_writer& writer) +std::unique_ptr +srsran::create_resource_grid_mapper(unsigned nof_ports, unsigned nof_subc, srsran::resource_grid_writer& writer) { return std::make_unique( - nof_ports, nof_symbols, nof_subc, writer, std::make_unique()); + nof_ports, nof_subc, writer, std::make_unique()); } \ No newline at end of file diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_avx2.cpp b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_avx2.cpp index 1c13acf084..43e1099f57 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_avx2.cpp +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_avx2.cpp @@ -23,6 +23,7 @@ #include "ldpc_encoder_avx2.h" #include "avx2_support.h" #include "srsran/srsvec/binary.h" +#include "srsran/srsvec/bit.h" #include "srsran/srsvec/circ_shift.h" #include "srsran/srsvec/copy.h" #include "srsran/srsvec/zero.h" @@ -140,7 +141,7 @@ void ldpc_encoder_avx2::select_strategy() ext_region = select_ext_strategy(node_size_avx2); } -void ldpc_encoder_avx2::load_input(span in) +void ldpc_encoder_avx2::load_input(const bit_buffer& in) { unsigned node_size_byte = node_size_avx2 * AVX2_SIZE_BYTE; unsigned tail_bytes = node_size_byte - lifting_size; @@ -149,12 +150,11 @@ void ldpc_encoder_avx2::load_input(span in) codeblock_used_size = codeblock_length / lifting_size * node_size_avx2; length_extended = (codeblock_length / lifting_size - bg_K) * node_size_avx2; - span codeblock(codeblock_buffer.data(), codeblock_used_size * AVX2_SIZE_BYTE); - span in_tmp = in; + span codeblock(codeblock_buffer.data(), codeblock_used_size * AVX2_SIZE_BYTE); + // Unpack the input bits into the CB buffer. for (unsigned i_node = 0; i_node != bg_K; ++i_node) { - srsvec::copy(codeblock.first(lifting_size), in_tmp.first(lifting_size)); + srsvec::bit_unpack(codeblock.first(lifting_size), in, i_node * lifting_size); codeblock = codeblock.last(codeblock.size() - lifting_size); - in_tmp = in_tmp.last(in_tmp.size() - lifting_size); srsvec::zero(codeblock.first(tail_bytes)); codeblock = codeblock.last(codeblock.size() - tail_bytes); @@ -182,27 +182,6 @@ static void fast_xor(span out, span in0, span out, span in) -{ - unsigned nof_vectors = in.size() / AVX2_SIZE_BYTE; - mm256::avx2_const_span in_local(in, nof_vectors); - mm256::avx2_span out_local(out, nof_vectors); - - __m256i all_ones = _mm256_set1_epi8(1); - - unsigned i = 0; - for (unsigned i_vector = 0; i_vector != nof_vectors; ++i_vector, i += AVX2_SIZE_BYTE) { - _mm256_storeu_si256(out_local.data_at(i_vector), _mm256_and_si256(in_local.get_at(i_vector), all_ones)); - } - - for (unsigned i_end = out.size(); i != i_end; ++i) { - out[i] = in[i] & 1; - } -} - template void ldpc_encoder_avx2::systematic_bits_inner() { @@ -232,8 +211,8 @@ void ldpc_encoder_avx2::systematic_bits_inner() if (i_blk != current_i_blk) { current_i_blk = i_blk; - fast_and_one(blk.first(lifting_size), codeblock.plain_span(current_i_blk, lifting_size)); - fast_and_one(blk.last(lifting_size), codeblock.plain_span(current_i_blk, lifting_size)); + srsvec::copy(blk.first(lifting_size), codeblock.plain_span(current_i_blk, lifting_size)); + srsvec::copy(blk.last(lifting_size), codeblock.plain_span(current_i_blk, lifting_size)); } auto set_plain_auxiliary = [this, auxiliary, m, i_aux]() { @@ -446,22 +425,23 @@ void ldpc_encoder_avx2::ext_region_inner() } } -void ldpc_encoder_avx2::write_codeblock(span out) +void ldpc_encoder_avx2::write_codeblock(bit_buffer& out) { unsigned nof_nodes = codeblock_length / lifting_size; // The first two blocks are shortened and the last node is not considered, since it can be incomplete. unsigned node_size_byte = node_size_avx2 * AVX2_SIZE_BYTE; - span out_tmp = out; + unsigned out_offset = 0; span codeblock(codeblock_buffer); codeblock = codeblock.last(codeblock.size() - 2 * node_size_byte); for (unsigned i_node = 2, max_i_node = nof_nodes - 1; i_node != max_i_node; ++i_node) { - srsvec::copy(out_tmp.first(lifting_size), codeblock.first(lifting_size)); - out_tmp = out_tmp.last(out_tmp.size() - lifting_size); + srsvec::bit_pack(out, out_offset, codeblock.first(lifting_size)); + codeblock = codeblock.last(codeblock.size() - node_size_byte); + out_offset += lifting_size; } // Take care of the last node. - unsigned remainder = out_tmp.size(); - srsvec::copy(out_tmp, codeblock.first(remainder)); + unsigned remainder = out.size() - out_offset; + srsvec::bit_pack(out, out_offset, codeblock.first(remainder)); } diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_avx2.h b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_avx2.h index a48e52d3ac..ed0de7565d 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_avx2.h +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_avx2.h @@ -35,11 +35,11 @@ class ldpc_encoder_avx2 : public ldpc_encoder_impl { private: void select_strategy() override; - void load_input(span in) override; + void load_input(const bit_buffer& in) override; void preprocess_systematic_bits() override { (this->*systematic_bits)(); } void encode_high_rate() override { (this->*high_rate)(); } void encode_ext_region() override { (this->*ext_region)(); } - void write_codeblock(span out) override; + void write_codeblock(bit_buffer& out) override; /// Alias for pointer to private methods. using strategy_method = void (ldpc_encoder_avx2::*)(); diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_generic.cpp b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_generic.cpp index 4e8e5a10a0..a7631b9d01 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_generic.cpp +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_generic.cpp @@ -22,6 +22,7 @@ #include "ldpc_encoder_generic.h" #include "srsran/srsvec/binary.h" +#include "srsran/srsvec/bit.h" #include "srsran/srsvec/copy.h" #include "srsran/srsvec/zero.h" @@ -78,7 +79,6 @@ void ldpc_encoder_generic::preprocess_systematic_bits() // for (uint16_t l = 0; l != lifting_size; ++l) { // uint16_t shifted_index = (node_shift + l) % lifting_size; // auxiliary[m][l] ^= message_chunk[shifted_index]; - // auxiliary[m][l] &= 1U; // } auto set_auxiliary_chunk = [this, m]() { if (m < bg_hr_parity_nodes) { @@ -94,7 +94,6 @@ void ldpc_encoder_generic::preprocess_systematic_bits() auxiliary_chunk.first(lifting_size - node_shift)); srsvec::binary_xor( auxiliary_chunk.last(node_shift), message_chunk.first(node_shift), auxiliary_chunk.last(node_shift)); - std::for_each(auxiliary_chunk.begin(), auxiliary_chunk.end(), [](uint8_t& v) { v &= 1U; }); } } @@ -118,12 +117,12 @@ void ldpc_encoder_generic::encode_ext_region() } } -void ldpc_encoder_generic::write_codeblock(span out) +void ldpc_encoder_generic::write_codeblock(bit_buffer& out) { // The encoder shortens the codeblock by discarding the first 2 * LS bits. uint8_t* first = &codeblock[2UL * lifting_size]; - srsvec::copy(out, span{first, out.size()}); + srsvec::bit_pack(out, span{first, out.size()}); } void ldpc_encoder_generic::high_rate_bg1_i6() diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_generic.h b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_generic.h index d74e8858c4..cedde09c72 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_generic.h +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_generic.h @@ -25,6 +25,7 @@ #pragma once #include "ldpc_encoder_impl.h" +#include "srsran/srsvec/bit.h" namespace srsran { @@ -32,11 +33,18 @@ namespace srsran { class ldpc_encoder_generic : public ldpc_encoder_impl { void select_strategy() override; - void load_input(span in) override { message = in; } + void load_input(const bit_buffer& in) override + { + span message_ = span(temp_message).first(in.size()); + srsvec::bit_unpack(message_, in); + + // From now on, the encoder only needs a read-only view of the input message. + message = message_; + } void preprocess_systematic_bits() override; void encode_high_rate() override { (this->*high_rate)(); } void encode_ext_region() override; - void write_codeblock(span out) override; + void write_codeblock(bit_buffer& out) override; /// Pointer type shortcut. using high_rate_strategy = void (ldpc_encoder_generic::*)(); @@ -52,7 +60,9 @@ class ldpc_encoder_generic : public ldpc_encoder_impl /// Carries out the high-rate region encoding for BG2 and lifting size index in {0, 1, 2, 4, 5, 6}. void high_rate_bg2_other(); - /// Local copy of the message to encode. + /// Unpacked local copy of the message to encode. + std::array temp_message = {}; + /// Read-only view of the message to encode. span message = {}; // Set up registers for the largest LS. /// Register to store auxiliary computation results. diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_impl.cpp b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_impl.cpp index 7c16f1dae3..e0c9146848 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_impl.cpp +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_impl.cpp @@ -41,8 +41,8 @@ void ldpc_encoder_impl::init(const codeblock_metadata::tb_common_metadata& cfg) select_strategy(); } -void ldpc_encoder_impl::encode(span output, - span input, +void ldpc_encoder_impl::encode(bit_buffer& output, + const bit_buffer& input, const codeblock_metadata::tb_common_metadata& cfg) { init(cfg); diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_impl.h b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_impl.h index 1a9d8e042c..f87e5fbb4c 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_impl.h +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_impl.h @@ -34,8 +34,7 @@ class ldpc_encoder_impl : public ldpc_encoder { public: // See interface for the documentation. - void - encode(span output, span input, const codeblock_metadata::tb_common_metadata& cfg) override; + void encode(bit_buffer& output, const bit_buffer& input, const codeblock_metadata::tb_common_metadata& cfg) override; private: /// Initializes the encoder inner variables. @@ -43,7 +42,7 @@ class ldpc_encoder_impl : public ldpc_encoder /// Selects the appropriate encoding strategy. virtual void select_strategy() {} /// Loads the input bits into the inner register. - virtual void load_input(span in) = 0; + virtual void load_input(const bit_buffer& in) = 0; /// Computes some intermediate variables required by the actual encoding. virtual void preprocess_systematic_bits() = 0; /// Computes the shortest possible codeword (systematic part plus high-rate region, that is the first @@ -52,7 +51,7 @@ class ldpc_encoder_impl : public ldpc_encoder /// Computes the rest of the redundancy bits (extension region). virtual void encode_ext_region() = 0; /// Moves relevant encoded bits from the internal register to the output vector. - virtual void write_codeblock(span out) = 0; + virtual void write_codeblock(bit_buffer& out) = 0; protected: /// Number of base graph parity nodes in the high-rate region. @@ -76,4 +75,4 @@ class ldpc_encoder_impl : public ldpc_encoder uint16_t codeblock_length = 52; }; -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_neon.cpp b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_neon.cpp index 0385c30bed..e070b29e37 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_neon.cpp +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_neon.cpp @@ -23,6 +23,7 @@ #include "ldpc_encoder_neon.h" #include "neon_support.h" #include "srsran/srsvec/binary.h" +#include "srsran/srsvec/bit.h" #include "srsran/srsvec/circ_shift.h" #include "srsran/srsvec/copy.h" #include "srsran/srsvec/zero.h" @@ -140,7 +141,7 @@ void ldpc_encoder_neon::select_strategy() ext_region = select_ext_strategy(node_size_neon); } -void ldpc_encoder_neon::load_input(span in) +void ldpc_encoder_neon::load_input(const bit_buffer& in) { unsigned node_size_byte = node_size_neon * NEON_SIZE_BYTE; unsigned tail_bytes = node_size_byte - lifting_size; @@ -149,12 +150,10 @@ void ldpc_encoder_neon::load_input(span in) codeblock_used_size = codeblock_length / lifting_size * node_size_neon; length_extended = (codeblock_length / lifting_size - bg_K) * node_size_neon; - span codeblock(codeblock_buffer.data(), codeblock_used_size * NEON_SIZE_BYTE); - span in_tmp = in; + span codeblock(codeblock_buffer.data(), codeblock_used_size * NEON_SIZE_BYTE); for (unsigned i_node = 0; i_node != bg_K; ++i_node) { - srsvec::copy(codeblock.first(lifting_size), in_tmp.first(lifting_size)); + srsvec::bit_unpack(codeblock.first(lifting_size), in, i_node * lifting_size); codeblock = codeblock.last(codeblock.size() - lifting_size); - in_tmp = in_tmp.last(in_tmp.size() - lifting_size); srsvec::zero(codeblock.first(tail_bytes)); codeblock = codeblock.last(codeblock.size() - tail_bytes); @@ -181,26 +180,6 @@ static void fast_xor(span out, span in0, span -// stored in "out". This is done to set filler bits (represented by a large even number) to zero, as understood > -// encoder. -static void fast_and_one(span out, span in) -{ - unsigned nof_vectors = in.size() / NEON_SIZE_BYTE; - neon::neon_const_span in_local(in, nof_vectors); - neon::neon_span out_local(out, nof_vectors); - const int8x16_t all_ones = vdupq_n_s8(1); - - unsigned i = 0; - for (unsigned i_vector = 0; i_vector != nof_vectors; ++i_vector, i += NEON_SIZE_BYTE) { - vst1q_s8(out_local.data_at(i_vector, 0), vandq_s8(in_local.get_at(i_vector), all_ones)); - } - - for (unsigned i_end = out.size(); i != i_end; ++i) { - out[i] = in[i] & 1; - } -} - template void ldpc_encoder_neon::systematic_bits_inner() { @@ -230,8 +209,8 @@ void ldpc_encoder_neon::systematic_bits_inner() if (i_blk != current_i_blk) { current_i_blk = i_blk; - fast_and_one(blk.first(lifting_size), codeblock.plain_span(current_i_blk, lifting_size)); - fast_and_one(blk.last(lifting_size), codeblock.plain_span(current_i_blk, lifting_size)); + srsvec::copy(blk.first(lifting_size), codeblock.plain_span(current_i_blk, lifting_size)); + srsvec::copy(blk.last(lifting_size), codeblock.plain_span(current_i_blk, lifting_size)); } auto set_plain_auxiliary = [this, auxiliary, m, i_aux]() { @@ -444,22 +423,23 @@ void ldpc_encoder_neon::ext_region_inner() } } -void ldpc_encoder_neon::write_codeblock(span out) +void ldpc_encoder_neon::write_codeblock(bit_buffer& out) { unsigned nof_nodes = codeblock_length / lifting_size; // The first two blocks are shortened and the last node is not considered, since it can be incomplete. unsigned node_size_byte = node_size_neon * NEON_SIZE_BYTE; - span out_tmp = out; + unsigned out_offset = 0; span codeblock(codeblock_buffer); codeblock = codeblock.last(codeblock.size() - 2 * node_size_byte); for (unsigned i_node = 2, max_i_node = nof_nodes - 1; i_node != max_i_node; ++i_node) { - srsvec::copy(out_tmp.first(lifting_size), codeblock.first(lifting_size)); - out_tmp = out_tmp.last(out_tmp.size() - lifting_size); + srsvec::bit_pack(out, out_offset, codeblock.first(lifting_size)); + codeblock = codeblock.last(codeblock.size() - node_size_byte); + out_offset += lifting_size; } // Take care of the last node. - unsigned remainder = out_tmp.size(); - srsvec::copy(out_tmp, codeblock.first(remainder)); + unsigned remainder = out.size() - out_offset; + srsvec::bit_pack(out, out_offset, codeblock.first(remainder)); } diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_neon.h b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_neon.h index ebe4cfde83..6d4a3ab68a 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_neon.h +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_encoder_neon.h @@ -35,11 +35,11 @@ class ldpc_encoder_neon : public ldpc_encoder_impl { private: void select_strategy() override; - void load_input(span in) override; + void load_input(const bit_buffer& in) override; void preprocess_systematic_bits() override { (this->*systematic_bits)(); } void encode_high_rate() override { (this->*high_rate)(); } void encode_ext_region() override { (this->*ext_region)(); } - void write_codeblock(span out) override; + void write_codeblock(bit_buffer& out) override; /// Alias for pointer to private methods. using strategy_method = void (ldpc_encoder_neon::*)(); diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_rate_matcher_impl.cpp b/lib/phy/upper/channel_coding/ldpc/ldpc_rate_matcher_impl.cpp index b0fc3fad04..f15f57f9fb 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_rate_matcher_impl.cpp +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_rate_matcher_impl.cpp @@ -87,18 +87,16 @@ void ldpc_rate_matcher_impl::init(const codeblock_metadata& cfg, unsigned block_ shift_k0 = static_cast(floor(tmp)) * lifting_size; } -void ldpc_rate_matcher_impl::rate_match(bit_buffer& output, span input, const codeblock_metadata& cfg) +void ldpc_rate_matcher_impl::rate_match(bit_buffer& output, const bit_buffer& input, const codeblock_metadata& cfg) { init(cfg, input.size(), output.size()); - buffer = input.first(buffer_length); - span aux = span(auxiliary_buffer).first(output.size()); - select_bits(aux, buffer); + select_bits(aux, input.first(buffer_length)); interleave_bits(output, aux); } -void ldpc_rate_matcher_impl::select_bits(span out, span in) const +void ldpc_rate_matcher_impl::select_bits(span out, const bit_buffer& in) const { unsigned output_length = out.size(); unsigned out_index = 0; @@ -134,7 +132,7 @@ void ldpc_rate_matcher_impl::select_bits(span out, span unsigned count = std::min(input_chunk_range.length(), output_length - out_index); // Append the consecutive number of bits. - srsvec::copy(out.subspan(out_index, count), in.subspan(in_index, count)); + srsvec::bit_unpack(out.subspan(out_index, count), in, in_index); out_index += count; // Advance in_index the amount of written bits. diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_rate_matcher_impl.h b/lib/phy/upper/channel_coding/ldpc/ldpc_rate_matcher_impl.h index 9a3d23d131..7a60928f5a 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_rate_matcher_impl.h +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_rate_matcher_impl.h @@ -35,7 +35,7 @@ class ldpc_rate_matcher_impl : public ldpc_rate_matcher { public: // See interface for the documentation. - void rate_match(bit_buffer& output, span input, const codeblock_metadata& cfg) override; + void rate_match(bit_buffer& output, const bit_buffer& input, const codeblock_metadata& cfg) override; private: /// Initializes the rate matcher internal state. @@ -44,8 +44,8 @@ class ldpc_rate_matcher_impl : public ldpc_rate_matcher /// \brief Carries out bit selection, as per TS38.212 Section 5.4.2.1. /// /// \param[out] out Sequence of selected bits. - /// \param[in] in Input codeblock. - void select_bits(span out, span in) const; + /// \param[in] in Input encoded code block. + void select_bits(span out, const bit_buffer& in) const; /// \brief Carries out bit interleaving, as per TS38.212 Section 5.4.2.2. /// @@ -55,8 +55,6 @@ class ldpc_rate_matcher_impl : public ldpc_rate_matcher // Data members - /// Bit selection circular buffer. - span buffer = {}; /// Auxiliary buffer. std::array auxiliary_buffer = {}; /// Redundancy version, values in {0, 1, 2, 3}. diff --git a/lib/phy/upper/channel_coding/ldpc/ldpc_segmenter_impl.cpp b/lib/phy/upper/channel_coding/ldpc/ldpc_segmenter_impl.cpp index f4fc143357..7362bac0c6 100644 --- a/lib/phy/upper/channel_coding/ldpc/ldpc_segmenter_impl.cpp +++ b/lib/phy/upper/channel_coding/ldpc/ldpc_segmenter_impl.cpp @@ -21,6 +21,7 @@ */ #include "ldpc_segmenter_impl.h" +#include "srsran/phy/upper/channel_coding/ldpc/ldpc.h" #include "srsran/phy/upper/codeblock_metadata.h" #include "srsran/srsvec/bit.h" #include "srsran/srsvec/copy.h" @@ -316,13 +317,9 @@ codeblock_metadata ldpc_segmenter_impl::generate_cb_metadata(const segment_inter tmp_description.tb_common.Nref = cfg.Nref; tmp_description.tb_common.cw_length = seg_extra.cw_length.value(); - // BG1 has rate 1/3 and BG2 has rate 1/5. - constexpr unsigned INVERSE_BG1_RATE = 3; - constexpr unsigned INVERSE_BG2_RATE = 5; - unsigned inverse_rate = (base_graph == ldpc_base_graph_type::BG1) ? INVERSE_BG1_RATE : INVERSE_BG2_RATE; - unsigned rm_length = compute_rm_length(seg_extra.i_segment, cfg.mod, cfg.nof_layers); + unsigned rm_length = compute_rm_length(seg_extra.i_segment, cfg.mod, cfg.nof_layers); - tmp_description.cb_specific.full_length = segment_length.value() * inverse_rate; + tmp_description.cb_specific.full_length = compute_full_codeblock_size(base_graph, segment_length).value(); tmp_description.cb_specific.nof_filler_bits = seg_extra.nof_filler_bits.value(); tmp_description.cb_specific.rm_length = rm_length; tmp_description.cb_specific.cw_offset = seg_extra.cw_offset; diff --git a/lib/phy/upper/channel_modulation/modulation_mapper_impl.cpp b/lib/phy/upper/channel_modulation/modulation_mapper_impl.cpp index 49cdfe4adb..004a2f4273 100644 --- a/lib/phy/upper/channel_modulation/modulation_mapper_impl.cpp +++ b/lib/phy/upper/channel_modulation/modulation_mapper_impl.cpp @@ -240,6 +240,25 @@ void modulation_mapper_impl::modulate(span symbols, const bit_buffer& inpu } } +float srsran::modulation_mapper::get_modulation_scaling(modulation_scheme modulation) +{ + switch (modulation) { + case modulation_scheme::PI_2_BPSK: + return M_SQRT1_2; + case modulation_scheme::BPSK: + return M_SQRT1_2; + case modulation_scheme::QPSK: + return qpsk_modulator.scaling; + case modulation_scheme::QAM16: + return qam16_modulator.scaling; + case modulation_scheme::QAM64: + return qam64_modulator.scaling; + case modulation_scheme::QAM256: + default: + return qam256_modulator.scaling; + } +} + float modulation_mapper_impl::modulate(span symbols, const bit_buffer& input, modulation_scheme scheme) { srsran_assert(input.size() == get_bits_per_symbol(scheme) * symbols.size(), diff --git a/lib/phy/upper/channel_processors/CMakeLists.txt b/lib/phy/upper/channel_processors/CMakeLists.txt index d84476cbdc..10d4f55a03 100644 --- a/lib/phy/upper/channel_processors/CMakeLists.txt +++ b/lib/phy/upper/channel_processors/CMakeLists.txt @@ -46,7 +46,7 @@ add_library(srsran_pdsch_processor STATIC pdsch_processor_concurrent_impl.cpp pdsch_processor_impl.cpp pdsch_processor_lite_impl.cpp) -target_link_libraries(srsran_pdsch_processor srsran_upper_phy_support) +target_link_libraries(srsran_pdsch_processor srsran_upper_phy_support srsran_channel_modulation) add_library(srsran_prach_detector STATIC prach_detector_generic_impl.cpp @@ -79,12 +79,9 @@ target_link_libraries(srsran_channel_processors srsran_pucch_demodulator srsran_pucch_detector srsran_pucch_processor - srsran_pusch_decoder - srsran_pusch_demodulator srsran_pusch_processor srsran_ssb_processor - srsran_uci_decoder - srsran_ulsch_demux) + srsran_uci_decoder) install(TARGETS srsran_channel_processors srsran_pbch_encoder @@ -99,10 +96,7 @@ install(TARGETS srsran_channel_processors srsran_pucch_demodulator srsran_pucch_detector srsran_pucch_processor - srsran_pusch_decoder - srsran_pusch_demodulator srsran_pusch_processor srsran_ssb_processor srsran_uci_decoder - srsran_ulsch_demux EXPORT srsran_export) \ No newline at end of file diff --git a/lib/phy/upper/channel_processors/channel_processor_factories.cpp b/lib/phy/upper/channel_processors/channel_processor_factories.cpp index 95cab0f433..2f26a8da18 100644 --- a/lib/phy/upper/channel_processors/channel_processor_factories.cpp +++ b/lib/phy/upper/channel_processors/channel_processor_factories.cpp @@ -31,6 +31,7 @@ #include "pdsch_processor_concurrent_impl.h" #include "pdsch_processor_impl.h" #include "pdsch_processor_lite_impl.h" +#include "pdsch_processor_pool.h" #include "prach_detector_generic_impl.h" #include "prach_generator_impl.h" #include "pucch_demodulator_impl.h" @@ -39,6 +40,7 @@ #include "pusch/pusch_decoder_impl.h" #include "pusch/pusch_demodulator_impl.h" #include "pusch/pusch_processor_impl.h" +#include "pusch/pusch_processor_pool.h" #include "pusch/ulsch_demultiplex_impl.h" #include "ssb_processor_impl.h" #include "uci_decoder_impl.h" @@ -316,59 +318,62 @@ class pdsch_processor_factory_sw : public pdsch_processor_factory class pdsch_processor_concurrent_factory_sw : public pdsch_processor_factory { private: - std::shared_ptr segmenter_factory; - std::shared_ptr encoder_factory; - std::shared_ptr rate_matcher_factory; - std::shared_ptr prg_factory; - std::shared_ptr modulator_factory; - std::shared_ptr dmrs_factory; - task_executor& executor; - unsigned nof_concurrent_threads; + std::shared_ptr crc_factory; + std::shared_ptr encoder_factory; + std::shared_ptr rate_matcher_factory; + std::shared_ptr prg_factory; + std::shared_ptr modulator_factory; + std::shared_ptr dmrs_factory; + task_executor& executor; + std::shared_ptr cb_processor_pool; public: - pdsch_processor_concurrent_factory_sw(std::shared_ptr segmenter_factory_, + pdsch_processor_concurrent_factory_sw(std::shared_ptr crc_factory_, std::shared_ptr encoder_factory_, std::shared_ptr rate_matcher_factory_, std::shared_ptr prg_factory_, std::shared_ptr modulator_factory_, std::shared_ptr dmrs_factory_, task_executor& executor_, - unsigned nof_concurrent_threads_) : - segmenter_factory(std::move(segmenter_factory_)), + unsigned nof_concurrent_threads) : + crc_factory(std::move(crc_factory_)), encoder_factory(std::move(encoder_factory_)), rate_matcher_factory(std::move(rate_matcher_factory_)), prg_factory(std::move(prg_factory_)), modulator_factory(std::move(modulator_factory_)), dmrs_factory(std::move(dmrs_factory_)), - executor(executor_), - nof_concurrent_threads(nof_concurrent_threads_) + executor(executor_) { - srsran_assert(segmenter_factory, "Invalid segmenter factory."); + srsran_assert(crc_factory, "Invalid CRC calculator factory."); srsran_assert(encoder_factory, "Invalid encoder factory."); srsran_assert(rate_matcher_factory, "Invalid rate matcher factory."); srsran_assert(prg_factory, "Invalid PRG factory."); srsran_assert(modulator_factory, "Invalid modulator factory."); srsran_assert(dmrs_factory, "Invalid DM-RS factory."); srsran_assert(nof_concurrent_threads > 1, "Number of concurrent threads must be greater than one."); - } - std::unique_ptr create() override - { - // Create pool of encoders. - std::vector> cb_processor_pool; + // Create vector of codeblock processors. + std::vector> cb_processors; for (unsigned i_encoder = 0; i_encoder != nof_concurrent_threads; ++i_encoder) { - cb_processor_pool.emplace_back( - std::make_unique(encoder_factory->create(), + cb_processors.emplace_back( + std::make_unique(crc_factory->create(crc_generator_poly::CRC24A), + crc_factory->create(crc_generator_poly::CRC24B), + crc_factory->create(crc_generator_poly::CRC16), + encoder_factory->create(), rate_matcher_factory->create(), prg_factory->create(), modulator_factory->create_modulation_mapper())); } - return std::make_unique(segmenter_factory->create(), - std::move(cb_processor_pool), - prg_factory->create(), - dmrs_factory->create(), - executor); + // Create pool of codeblock processors. It is common for all PDSCH processors. + cb_processor_pool = + std::make_shared(std::move(cb_processors)); + } + + std::unique_ptr create() override + { + return std::make_unique( + cb_processor_pool, prg_factory->create(), dmrs_factory->create(), executor); } std::unique_ptr create_validator() override @@ -425,6 +430,47 @@ class pdsch_processor_lite_factory_sw : public pdsch_processor_factory } }; +class pdsch_processor_pool_factory : public pdsch_processor_factory +{ +public: + pdsch_processor_pool_factory(std::shared_ptr factory_, unsigned max_nof_processors_) : + factory(std::move(factory_)), max_nof_processors(max_nof_processors_) + { + srsran_assert(factory, "Invalid PDSCH processor factory."); + srsran_assert(max_nof_processors >= 1, + "The number of processors (i.e., {}) must be greater than or equal to one.", + max_nof_processors); + } + + std::unique_ptr create() override + { + // Create processors. + std::vector> processors(max_nof_processors); + for (std::unique_ptr& processor : processors) { + processor = factory->create(); + } + + return std::make_unique(processors); + } + + std::unique_ptr create(srslog::basic_logger& logger, bool enable_logging_broadcast) override + { + // Create processors with logging. + std::vector> processors(max_nof_processors); + for (std::unique_ptr& processor : processors) { + processor = factory->create(logger, enable_logging_broadcast); + } + + return std::make_unique(processors); + } + + std::unique_ptr create_validator() override { return factory->create_validator(); } + +private: + std::shared_ptr factory; + unsigned max_nof_processors; +}; + class prach_detector_factory_sw : public prach_detector_factory { private: @@ -559,7 +605,7 @@ class pucch_processor_factory_sw : public pucch_processor_factory } private: - static constexpr unsigned MAX_PUCCH_RX_PORTS = 1; + static constexpr unsigned MAX_PUCCH_RX_PORTS = 4; std::shared_ptr dmrs_factory; std::shared_ptr detector_factory; std::shared_ptr demodulator_factory; @@ -569,23 +615,30 @@ class pucch_processor_factory_sw : public pucch_processor_factory class pusch_decoder_factory_sw : public pusch_decoder_factory { -private: - std::shared_ptr crc_factory; - std::shared_ptr decoder_factory; - std::shared_ptr dematcher_factory; - std::shared_ptr segmenter_factory; - public: - explicit pusch_decoder_factory_sw(pusch_decoder_factory_sw_configuration& config) : + explicit pusch_decoder_factory_sw(pusch_decoder_factory_sw_configuration config) : crc_factory(std::move(config.crc_factory)), - decoder_factory(std::move(config.decoder_factory)), - dematcher_factory(std::move(config.dematcher_factory)), - segmenter_factory(std::move(config.segmenter_factory)) + segmenter_factory(std::move(config.segmenter_factory)), + executor(config.executor) { srsran_assert(crc_factory, "Invalid CRC calculator factory."); - srsran_assert(decoder_factory, "Invalid LDPC decoder factory."); - srsran_assert(dematcher_factory, "Invalid LDPC dematcher factory."); + srsran_assert(config.decoder_factory, "Invalid LDPC decoder factory."); + srsran_assert(config.dematcher_factory, "Invalid LDPC dematcher factory."); srsran_assert(segmenter_factory, "Invalid LDPC segmenter factory."); + + std::vector> codeblock_decoders( + std::max(1U, config.nof_pusch_decoder_threads)); + for (std::unique_ptr& codeblock_decoder : codeblock_decoders) { + pusch_codeblock_decoder::sch_crc crcs1; + crcs1.crc16 = crc_factory->create(crc_generator_poly::CRC16); + crcs1.crc24A = crc_factory->create(crc_generator_poly::CRC24A); + crcs1.crc24B = crc_factory->create(crc_generator_poly::CRC24B); + + codeblock_decoder = std::make_unique( + config.dematcher_factory->create(), config.decoder_factory->create(), crcs1); + } + + decoder_pool = std::make_unique(std::move(codeblock_decoders)); } std::unique_ptr create() override @@ -594,9 +647,15 @@ class pusch_decoder_factory_sw : public pusch_decoder_factory crcs.crc16 = crc_factory->create(crc_generator_poly::CRC16); crcs.crc24A = crc_factory->create(crc_generator_poly::CRC24A); crcs.crc24B = crc_factory->create(crc_generator_poly::CRC24B); - return std::make_unique( - segmenter_factory->create(), dematcher_factory->create(), decoder_factory->create(), std::move(crcs)); + + return std::make_unique(segmenter_factory->create(), decoder_pool, std::move(crcs), executor); } + +private: + std::shared_ptr decoder_pool; + std::shared_ptr crc_factory; + std::shared_ptr segmenter_factory; + task_executor* executor; }; class pusch_demodulator_factory_sw : public pusch_demodulator_factory @@ -692,6 +751,50 @@ class pusch_processor_factory_sw : public pusch_processor_factory channel_state_information::sinr_type csi_sinr_calc_method; }; +class pusch_processor_pool_factory : public pusch_processor_factory +{ +public: + pusch_processor_pool_factory(std::shared_ptr factory_, unsigned max_nof_processors_) : + factory(std::move(factory_)), max_nof_processors(max_nof_processors_) + { + srsran_assert(factory, "Invalid PUSCH factory."); + } + + std::unique_ptr create() override + { + if (max_nof_processors <= 1) { + return factory->create(); + } + + std::vector> processors(max_nof_processors); + for (std::unique_ptr& processor : processors) { + processor = factory->create(); + } + + return std::make_unique(processors); + } + + std::unique_ptr create(srslog::basic_logger& logger) override + { + if (max_nof_processors <= 1) { + return factory->create(logger); + } + + std::vector> processors(max_nof_processors); + for (std::unique_ptr& processor : processors) { + processor = factory->create(logger); + } + + return std::make_unique(processors); + } + + std::unique_ptr create_validator() override { return factory->create_validator(); } + +private: + std::shared_ptr factory; + unsigned max_nof_processors; +}; + class ssb_processor_factory_sw : public ssb_processor_factory { public: @@ -863,7 +966,7 @@ srsran::create_pdsch_processor_factory_sw(std::shared_ptr } std::shared_ptr -srsran::create_pdsch_concurrent_processor_factory_sw(std::shared_ptr segmenter_factory, +srsran::create_pdsch_concurrent_processor_factory_sw(std::shared_ptr crc_factory, std::shared_ptr ldpc_enc_factory, std::shared_ptr ldpc_rm_factory, std::shared_ptr prg_factory, @@ -872,7 +975,7 @@ srsran::create_pdsch_concurrent_processor_factory_sw(std::shared_ptr(std::move(segmenter_factory), + return std::make_shared(std::move(crc_factory), std::move(ldpc_enc_factory), std::move(ldpc_rm_factory), std::move(prg_factory), @@ -898,6 +1001,13 @@ srsran::create_pdsch_lite_processor_factory_sw(std::shared_ptr +srsran::create_pdsch_processor_pool(std::shared_ptr pdsch_proc_factory, + unsigned max_nof_processors) +{ + return std::make_shared(std::move(pdsch_proc_factory), max_nof_processors); +} + std::shared_ptr srsran::create_prach_detector_factory_sw(std::shared_ptr dft_factory, std::shared_ptr prach_gen_factory, @@ -940,9 +1050,9 @@ srsran::create_pucch_detector_factory_sw(std::shared_ptr -srsran::create_pusch_decoder_factory_sw(pusch_decoder_factory_sw_configuration& config) +srsran::create_pusch_decoder_factory_sw(pusch_decoder_factory_sw_configuration config) { - return std::make_shared(config); + return std::make_shared(std::move(config)); } std::shared_ptr @@ -965,6 +1075,12 @@ srsran::create_pusch_processor_factory_sw(pusch_processor_factory_sw_configurati return std::make_shared(config); } +std::shared_ptr +srsran::create_pusch_processor_pool(std::shared_ptr factory, unsigned max_nof_processors) +{ + return std::make_shared(std::move(factory), max_nof_processors); +} + std::shared_ptr srsran::create_ssb_processor_factory_sw(ssb_processor_factory_sw_configuration& config) { @@ -1049,7 +1165,7 @@ class logging_pdcch_processor_decorator : public pdcch_processor std::unique_ptr processor; }; -class logging_pdsch_processor_decorator : public pdsch_processor +class logging_pdsch_processor_decorator : public pdsch_processor, private pdsch_processor_notifier { public: logging_pdsch_processor_decorator(srslog::basic_logger& logger_, @@ -1061,37 +1177,56 @@ class logging_pdsch_processor_decorator : public pdsch_processor } void process(resource_grid_mapper& mapper, - static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, - const pdu_t& pdu) override + pdsch_processor_notifier& notifier_, + static_vector, MAX_NOF_TRANSPORT_BLOCKS> data_, + const pdu_t& pdu_) override { - const auto&& func = [&]() { processor->process(mapper, data, pdu); }; + notifier = ¬ifier_; + data = data_.front(); + pdu = pdu_; - if (!enable_logging_broadcast && is_broadcast_rnti(pdu.rnti)) { - func(); - return; - } + start = std::chrono::steady_clock::now(); + processor->process(mapper, *this, data_, pdu); + } - std::chrono::nanoseconds time_ns = time_execution(func); +private: + void on_finish_processing() override + { + // Finish time measurement. + auto end = std::chrono::steady_clock::now(); - if (logger.debug.enabled()) { - // Detailed log information, including a list of all PDU fields. - logger.debug(data.front().data(), - data.front().size(), - "PDSCH: {:s} tbs={} {}\n {:n}", - pdu, - data.front().size(), - time_ns, - pdu); - return; + // Only print if it is allowed. + if (enable_logging_broadcast || !is_broadcast_rnti(pdu.rnti)) { + // Get elapsed time. + std::chrono::nanoseconds time_ns = std::chrono::duration_cast(end - start); + + if (logger.debug.enabled()) { + // Detailed log information, including a list of all PDU fields. + logger.debug(data.data(), data.size(), "PDSCH: {:s} tbs={} {}\n {:n}", pdu, data.size(), time_ns, pdu); + } else { + // Single line log entry. + logger.info(data.data(), data.size(), "PDSCH: {:s} tbs={} {}", pdu, data.size(), time_ns); + } } - // Single line log entry. - logger.info(data.front().data(), data.front().size(), "PDSCH: {:s} tbs={} {}", pdu, data.front().size(), time_ns); + + // Verify the notifier is valid. + srsran_assert(notifier != nullptr, "Detected PDSCH processor notified twice."); + + // Set notifier to invalid pointer before notification. + pdsch_processor_notifier* notifier_ = notifier; + notifier = nullptr; + + // Notify original callback. Processor will be available after returning. + notifier_->on_finish_processing(); } -private: - srslog::basic_logger& logger; - bool enable_logging_broadcast; - std::unique_ptr processor; + srslog::basic_logger& logger; + bool enable_logging_broadcast; + std::unique_ptr processor; + pdsch_processor_notifier* notifier; + span data; + pdu_t pdu; + std::chrono::time_point start; }; class logging_prach_detector_decorator : public prach_detector @@ -1131,32 +1266,8 @@ class logging_prach_detector_decorator : public prach_detector std::unique_ptr detector; }; -class logging_pusch_processor_decorator : public pusch_processor +class logging_pusch_processor_decorator : public pusch_processor, private pusch_processor_result_notifier { - class pusch_processor_result_notifier_wrapper : public pusch_processor_result_notifier - { - public: - pusch_processor_result_notifier_wrapper(pusch_processor_result_notifier& notifier_) : notifier(notifier_) {} - - void on_uci(const pusch_processor_result_control& uci) override - { - results.uci.emplace(uci); - notifier.on_uci(uci); - } - - void on_sch(const pusch_processor_result_data& sch) override - { - results.sch.emplace(sch); - notifier.on_sch(sch); - } - - const fmt::pusch_results_wrapper& get_results() const { return results; } - - private: - fmt::pusch_results_wrapper results; - pusch_processor_result_notifier& notifier; - }; - public: logging_pusch_processor_decorator(srslog::basic_logger& logger_, std::unique_ptr processor_) : logger(logger_), processor(std::move(processor_)) @@ -1164,19 +1275,35 @@ class logging_pusch_processor_decorator : public pusch_processor srsran_assert(processor, "Invalid processor."); } - void process(span data, - rx_softbuffer& softbuffer, - pusch_processor_result_notifier& notifier, + void process(span data_, + unique_rx_softbuffer softbuffer, + pusch_processor_result_notifier& notifier_, const resource_grid_reader& grid, - const pdu_t& pdu) override + const pdu_t& pdu_) override { - pusch_processor_result_notifier_wrapper notifier_wrapper(notifier); + notifier = ¬ifier_; + data = data_; + pdu = pdu_; + time_start = std::chrono::steady_clock::now(); + time_uci = std::chrono::time_point(); + results = {}; - std::chrono::nanoseconds time_ns = time_execution([this, ¬ifier_wrapper, &data, &softbuffer, &grid, &pdu]() { - processor->process(data, softbuffer, notifier_wrapper, grid, pdu); - }); + processor->process(data, std::move(softbuffer), *this, grid, pdu); + time_return = std::chrono::steady_clock::now(); + } - const auto& results = notifier_wrapper.get_results(); +private: + void on_uci(const pusch_processor_result_control& uci) override + { + srsran_assert(notifier, "Invalid notifier"); + time_uci = std::chrono::steady_clock::now(); + results.uci = uci; + notifier->on_uci(uci); + } + + void on_sch(const pusch_processor_result_data& sch) override + { + srsran_assert(notifier, "Invalid notifier"); // Data size in bytes for printing hex dump only if SCH is present and CRC is passed. unsigned data_size = 0; @@ -1184,26 +1311,69 @@ class logging_pusch_processor_decorator : public pusch_processor data_size = data.size(); } + // Save SCH results. + results.sch = sch; + + std::chrono::time_point time_end = std::chrono::steady_clock::now(); + + // Calculate the UCI report latency if available. + std::chrono::nanoseconds time_uci_ns = {}; + if (time_uci != std::chrono::time_point()) { + time_uci_ns = time_uci - time_start; + } + + // Calculate the return latency if available. + std::chrono::nanoseconds time_return_ns = {}; + if (time_return != std::chrono::time_point()) { + time_return_ns = time_return - time_start; + } + + // Calculate the final time. + std::chrono::nanoseconds time_ns = time_end - time_start; + if (logger.debug.enabled()) { // Detailed log information, including a list of all PDU fields. logger.debug(data.data(), data_size, - "PUSCH: {:s} tbs={} {:s} {}\n {:n}\n {:n}", + "PUSCH: {:s} tbs={} {:s} {} uci_{} ret_{}\n {:n}\n {:n}", pdu, data.size(), results, time_ns, + time_uci_ns, + time_return_ns, pdu, results); } else { // Single line log entry. - logger.info(data.data(), data_size, "PUSCH: {:s} tbs={} {:s} {}", pdu, data.size(), results, time_ns); + logger.info(data.data(), + data_size, + "PUSCH: {:s} tbs={} {:s} {} uci_{} ret_{}", + pdu, + data.size(), + results, + time_ns, + time_uci_ns, + time_return_ns); } + + // Exchanges the notifier before notifying the reception of SCH. + pusch_processor_result_notifier* notifier_ = nullptr; + std::exchange(notifier_, notifier); + + // Notify the SCH reception. + notifier_->on_sch(sch); } -private: - srslog::basic_logger& logger; - std::unique_ptr processor; + srslog::basic_logger& logger; + std::unique_ptr processor; + span data; + pdu_t pdu; + pusch_processor_result_notifier* notifier; + std::chrono::time_point time_start; + std::chrono::time_point time_uci; + std::chrono::time_point time_return; + fmt::pusch_results_wrapper results; }; class logging_pucch_processor_decorator : public pucch_processor diff --git a/lib/phy/upper/channel_processors/pdsch_codeblock_processor.cpp b/lib/phy/upper/channel_processors/pdsch_codeblock_processor.cpp index 7fb69cae76..f1e5291cca 100644 --- a/lib/phy/upper/channel_processors/pdsch_codeblock_processor.cpp +++ b/lib/phy/upper/channel_processors/pdsch_codeblock_processor.cpp @@ -24,24 +24,99 @@ using namespace srsran; -pseudo_random_generator::state_s pdsch_codeblock_processor::process(span buffer, - const described_segment& descr_seg, - pseudo_random_generator::state_s c_init) +pseudo_random_generator::state_s +pdsch_codeblock_processor::process(span buffer, span data, const configuration& config) { - // Initialize scrambling with the initial state. - scrambler->init(c_init); + using namespace units::literals; - // CB payload number of bits. - unsigned cb_length = descr_seg.get_data().size(); + // Initialize scrambling with the initial state. + scrambler->init(config.c_init); + + // Prepare codeblock data. + units::bits nof_used_bits = 0_bits; + cb_data.resize(config.cb_size.value()); + + // Calculate transport block size. + units::bits tbs = units::bytes(data.size()).to_bits(); + + // Verify range of the CB information bits. + srsran_assert( + (config.tb_offset + config.cb_info_size) <= tbs, + "TB offset (i.e., {}) and number of information bits (i.e., {}) exceeds the transport block size (i.e., {})", + config.tb_offset, + config.cb_info_size, + tbs); + + // Copy codeblock data. + { + bit_buffer message = cb_data.first(config.cb_info_size.value()); + srsvec::copy_offset(message, data, config.tb_offset.value()); + nof_used_bits += units::bits(config.cb_info_size); + } + + // Append transport block CRC if applicable. + if ((config.tb_offset + config.cb_info_size) == tbs) { + constexpr units::bits MAX_BITS_CRC16{3824}; + crc_calculator& tb_crc = (tbs <= MAX_BITS_CRC16) ? *crc16 : *crc24a; + units::bits nof_tb_crc_bits = units::bits(get_crc_size(tb_crc.get_generator_poly())); + + crc_calculator_checksum_t tb_checksum = tb_crc.calculate_byte(data); + for (unsigned i_checksum_byte = 0, i_checksum_byte_end = nof_tb_crc_bits.truncate_to_bytes().value(); + i_checksum_byte != i_checksum_byte_end; + ++i_checksum_byte) { + // Extract byte from the CRC. + unsigned tb_crc_byte = (tb_checksum >> (nof_tb_crc_bits.value() - (i_checksum_byte + 1) * 8)) & 0xffUL; + // Insert the byte at the end of the bit buffer. + cb_data.insert(tb_crc_byte, nof_used_bits.value(), 8); + // Increment the number of bits. + nof_used_bits += 8_bits; + } + + // Insert zero padding bits. + for (units::bits nof_used_bits_end = nof_used_bits + config.zero_pad; nof_used_bits != nof_used_bits_end;) { + // Calculate the number of zeros to pad, no more than a byte at a time. + units::bits nof_zeros = std::min(8_bits, nof_used_bits_end - nof_used_bits); + // Insert the zeros at the end of the bit buffer. + cb_data.insert(0UL, nof_used_bits.value(), nof_zeros.value()); + // Increment the number of bits. + nof_used_bits += nof_zeros; + } + } + + // Append codeblock CRC if applicable. + if (config.has_cb_crc) { + crc_calculator& cb_crc = *crc24b; + + crc_calculator_checksum_t cb_checksum = cb_crc.calculate(cb_data.first(nof_used_bits.value())); + for (unsigned i_checksum_byte = 0, i_checksum_byte_end = 3; i_checksum_byte != i_checksum_byte_end; + ++i_checksum_byte) { + // Extract byte from the CRC. + unsigned cb_crc_byte = (cb_checksum >> (24 - (i_checksum_byte + 1) * 8)) & 0xffUL; + // Insert the byte at the end of the bit buffer. + cb_data.insert(cb_crc_byte, nof_used_bits.value(), 8); + // Increment the number of bits. + nof_used_bits += 8_bits; + } + } + + // Append filler bits as zeros. + while (nof_used_bits != config.cb_size) { + // Calculate the number of zeros to pad, no more than a byte at a time. + units::bits nof_zeros = std::min(8_bits, units::bits(config.cb_size) - nof_used_bits); + // Insert the zeros at the end of the bit buffer. + cb_data.insert(0UL, nof_used_bits.value(), nof_zeros.value()); + // Increment the number of bits. + nof_used_bits += nof_zeros; + } // Rate Matching output length. - unsigned rm_length = descr_seg.get_metadata().cb_specific.rm_length; + unsigned rm_length = config.metadata.cb_specific.rm_length; // CB bit position within the codeword. - unsigned cw_offset_bit = descr_seg.get_metadata().cb_specific.cw_offset; + unsigned cw_offset_bit = config.metadata.cb_specific.cw_offset; // Extract modulation. - modulation_scheme modulation = descr_seg.get_metadata().tb_common.mod; + modulation_scheme modulation = config.metadata.tb_common.mod; // Number of bits per symbol. unsigned bits_per_symbol = get_bits_per_symbol(modulation); @@ -53,25 +128,15 @@ pseudo_random_generator::state_s pdsch_codeblock_processor::process(span // Number of modulated symbols. unsigned rm_length_symbol = rm_length / bits_per_symbol; - // Resize internal buffer to match data from the segmenter to the encoder (all segments have the same length). - span tmp_data = span(temp_unpacked_cb).first(cb_length); - // Resize internal buffer to match data from the encoder to the rate matcher (all segments have the same length). - span tmp_encoded = span(buffer_cb).first(descr_seg.get_metadata().cb_specific.full_length); - - // Unpack segment. - srsvec::bit_unpack(tmp_data, descr_seg.get_data()); - - // Set filler bits. - span filler_bits = tmp_data.last(descr_seg.get_metadata().cb_specific.nof_filler_bits); - std::fill(filler_bits.begin(), filler_bits.end(), ldpc::FILLER_BIT); + rm_buffer.resize(config.metadata.cb_specific.full_length); // Encode the segment into a codeblock. - encoder->encode(tmp_encoded, tmp_data, descr_seg.get_metadata().tb_common); + encoder->encode(rm_buffer, cb_data, config.metadata.tb_common); // Rate match the codeblock. temp_packed_bits.resize(rm_length); - rate_matcher->rate_match(temp_packed_bits, tmp_encoded, descr_seg.get_metadata()); + rate_matcher->rate_match(temp_packed_bits, rm_buffer, config.metadata); // Apply scrambling sequence. scrambler->apply_xor(temp_packed_bits, temp_packed_bits); diff --git a/lib/phy/upper/channel_processors/pdsch_codeblock_processor.h b/lib/phy/upper/channel_processors/pdsch_codeblock_processor.h index eb0e77adf8..cec1d41d80 100644 --- a/lib/phy/upper/channel_processors/pdsch_codeblock_processor.h +++ b/lib/phy/upper/channel_processors/pdsch_codeblock_processor.h @@ -22,7 +22,7 @@ #pragma once -#include "srsran/phy/support/re_buffer.h" +#include "srsran/phy/upper/channel_coding/crc_calculator.h" #include "srsran/phy/upper/channel_coding/ldpc/ldpc_encoder.h" #include "srsran/phy/upper/channel_coding/ldpc/ldpc_rate_matcher.h" #include "srsran/phy/upper/channel_modulation/modulation_mapper.h" @@ -35,20 +35,61 @@ namespace srsran { /// \brief PDSCH codeblock processor. /// -/// Contains the required dependencies to processes PDSCH codeblocks concurrently. +/// Contains the required dependencies to process PDSCH codeblocks in parallel. +/// +/// The codeblock processing consists of: +/// 1. Extracts the codeblock information bits from the transport block; +/// 2. Appends transport block CRC (if applicable); +/// 3. Appends zero padding; +/// 4. Appends codeblock CRC (if applicable); +/// 5. Appends filler bits as zeros; +/// 6. Applies LDPC encode; +/// 7. Applies LDPC rate matching; +/// 8. Applies scrambling; and +/// 9. Maps bits into modulation symbols. class pdsch_codeblock_processor { public: + /// Collects the parameters necessary for processing a PDSCH codeblock. + struct configuration { + /// Position of the codeblock relative to the first bit of the transport block. + units::bits tb_offset; + /// Codeblock number of information bits. + units::bits cb_info_size; + /// Codeblock size before LDPC encoder. + units::bits cb_size; + /// Transport block zero padding. + units::bits zero_pad; + /// Codeblock metadata. + codeblock_metadata metadata; + /// Set to true if codeblock CRC is applicable. + bool has_cb_crc; + /// Scrambling pseudo-random generator initial state. + pseudo_random_generator::state_s c_init; + }; + /// Builds a codeblock processor with the required dependencies. - pdsch_codeblock_processor(std::unique_ptr encoder_, + pdsch_codeblock_processor(std::unique_ptr crc24a_, + std::unique_ptr crc24b_, + std::unique_ptr crc16_, + std::unique_ptr encoder_, std::unique_ptr rate_matcher_, std::unique_ptr scrambler_, std::unique_ptr modulator_) : + crc24a(std::move(crc24a_)), + crc24b(std::move(crc24b_)), + crc16(std::move(crc16_)), encoder(std::move(encoder_)), rate_matcher(std::move(rate_matcher_)), scrambler(std::move(scrambler_)), modulator(std::move(modulator_)) { + srsran_assert(crc24a, "Invalid CRC calculator."); + srsran_assert(crc24b, "Invalid CRC calculator."); + srsran_assert(crc16, "Invalid CRC calculator."); + srsran_assert(crc24a->get_generator_poly() == crc_generator_poly::CRC24A, "Invalid CRC calculator."); + srsran_assert(crc24b->get_generator_poly() == crc_generator_poly::CRC24B, "Invalid CRC calculator."); + srsran_assert(crc16->get_generator_poly() == crc_generator_poly::CRC16, "Invalid CRC calculator."); srsran_assert(encoder, "Invalid LDPC encoder."); srsran_assert(rate_matcher, "Invalid LDPC rate matcher."); srsran_assert(scrambler, "Invalid scrambler."); @@ -57,15 +98,14 @@ class pdsch_codeblock_processor /// \brief Processes a PDSCH codeblock. /// - /// The PDSCH codeblock processing includes LDPC encoding, rate matching, bit packing, scrambling, modulation and - /// layer mapping. + /// The PDSCH codeblock processing includes transport and code block CRC attachment if applicable, LDPC encoding, + /// rate matching, bit packing, scrambling and modulation. /// /// \param[out] buffer Resource element buffer destination. - /// \param[in] descr_seg Description of the codeblock to be processed. - /// \param[in] c_init Scrambling initial state for the codeblocks to process. + /// \param[in] data Original transport block data without CRC. + /// \param[in] config Required parameters for processing a codeblock. /// \return The final pseudo-random generator scrambling state. - pseudo_random_generator::state_s - process(span buffer, const described_segment& descr_seg, pseudo_random_generator::state_s c_init); + pseudo_random_generator::state_s process(span buffer, span data, const configuration& config); /// Gets the QAM modulation scaling, as per TS38.211 Section 5.1. float get_scaling(modulation_scheme modulation) @@ -75,6 +115,12 @@ class pdsch_codeblock_processor } private: + /// Pointer to CRC24A. + std::unique_ptr crc24a; + /// Pointer to CRC24B. + std::unique_ptr crc24b; + /// Pointer to CRC16. + std::unique_ptr crc16; /// Pointer to an LDPC encoder. std::unique_ptr encoder; /// Pointer to an LDPC rate matcher. @@ -88,10 +134,12 @@ class pdsch_codeblock_processor /// /// This is the maximum length of an encoded codeblock, achievable with base graph 1 (rate 1/3). static constexpr units::bits MAX_CB_LENGTH{3 * MAX_SEG_LENGTH.value()}; - /// Buffer for storing temporary unpacked data between the LDPC segmenter and the LDPC encoder. - std::array temp_unpacked_cb = {}; + /// \brief Temporary codeblock message. + /// + /// It contains codeblock information bits, codeblock CRC (if applicable) and filler bits. + static_bit_buffer cb_data = {}; /// Buffer for storing temporary, full-length codeblocks, between the LDPC encoder and the LDPC rate matcher. - std::array buffer_cb = {}; + static_bit_buffer rm_buffer = {}; /// Buffer for storing temporary packed data between the LDPC rate matcher and the modulator. static_bit_buffer temp_packed_bits = {}; }; diff --git a/lib/phy/upper/channel_processors/pdsch_encoder_impl.cpp b/lib/phy/upper/channel_processors/pdsch_encoder_impl.cpp index 918aa78196..e31d064501 100644 --- a/lib/phy/upper/channel_processors/pdsch_encoder_impl.cpp +++ b/lib/phy/upper/channel_processors/pdsch_encoder_impl.cpp @@ -34,23 +34,13 @@ void pdsch_encoder_impl::encode(span codeword, // Segmentation (it includes CRC attachment for the entire transport block and each individual segment). segmenter->segment(d_segments, transport_block, cfg); - // Resize internal buffer to match data from the segmenter to the encoder (all segments have the same length). - span tmp_data = span(temp_unpacked_cb).first(d_segments[0].get_data().size()); - // Resize internal buffer to match data from the encoder to the rate matcher (all segments have the same length). - span tmp_encoded = span(buffer_cb).first(d_segments[0].get_metadata().cb_specific.full_length); + rm_buffer.resize(d_segments[0].get_metadata().cb_specific.full_length); unsigned offset = 0; for (const described_segment& descr_seg : d_segments) { - // Unpack segment. - srsvec::bit_unpack(tmp_data, descr_seg.get_data()); - - // Set filler bits. - span filler_bits = tmp_data.last(descr_seg.get_metadata().cb_specific.nof_filler_bits); - std::fill(filler_bits.begin(), filler_bits.end(), ldpc::FILLER_BIT); - // Encode the segment into a codeblock. - encoder->encode(tmp_encoded, tmp_data, descr_seg.get_metadata().tb_common); + encoder->encode(rm_buffer, descr_seg.get_data(), descr_seg.get_metadata().tb_common); // Select the correct chunk of the output codeword. unsigned rm_length = descr_seg.get_metadata().cb_specific.rm_length; @@ -59,7 +49,7 @@ void pdsch_encoder_impl::encode(span codeword, // Rate match the codeblock. codeblock_packed.resize(rm_length); - rate_matcher->rate_match(codeblock_packed, tmp_encoded, descr_seg.get_metadata()); + rate_matcher->rate_match(codeblock_packed, rm_buffer, descr_seg.get_metadata()); // Unpack code block. srsvec::bit_unpack(codeblock, codeblock_packed); diff --git a/lib/phy/upper/channel_processors/pdsch_encoder_impl.h b/lib/phy/upper/channel_processors/pdsch_encoder_impl.h index 2d2fa9f86c..07b6b2253c 100644 --- a/lib/phy/upper/channel_processors/pdsch_encoder_impl.h +++ b/lib/phy/upper/channel_processors/pdsch_encoder_impl.h @@ -69,12 +69,10 @@ class pdsch_encoder_impl : public pdsch_encoder /// /// This is the maximum length of an encoded codeblock, achievable with base graph 1 (rate 1/3). static constexpr units::bits MAX_CB_LENGTH{3 * MAX_SEG_LENGTH.value()}; - /// Buffer for storing temporary unpacked data between LDPC segmenter and the LDPC encoder. - std::array temp_unpacked_cb = {}; /// Buffer for storing temporary encoded and packed codeblock. static_bit_buffer codeblock_packed; /// Buffer for storing temporary, full-length codeblocks, between LDPC encoder and LDPC rate matcher. - std::array buffer_cb = {}; + static_bit_buffer rm_buffer = {}; }; } // namespace srsran diff --git a/lib/phy/upper/channel_processors/pdsch_processor_concurrent_impl.cpp b/lib/phy/upper/channel_processors/pdsch_processor_concurrent_impl.cpp index 56fbec244c..3cd8bf9dfd 100644 --- a/lib/phy/upper/channel_processors/pdsch_processor_concurrent_impl.cpp +++ b/lib/phy/upper/channel_processors/pdsch_processor_concurrent_impl.cpp @@ -23,17 +23,14 @@ #include "pdsch_processor_concurrent_impl.h" #include "srsran/phy/support/resource_grid_mapper.h" #include "srsran/ran/dmrs.h" -#include "srsran/srsvec/bit.h" -#include "srsran/srsvec/copy.h" -#include -#include using namespace srsran; -void pdsch_processor_concurrent_impl::map(resource_grid_mapper& mapper, - resource_grid_mapper::symbol_buffer& buffer, - const pdu_t& config) +void pdsch_processor_concurrent_impl::map(span codeword) { + // Build resource grid mapper adaptor. + resource_grid_mapper::symbol_buffer_adapter buffer(codeword); + // Get the PRB allocation mask. const bounded_bitset prb_allocation_mask = config.freq_alloc.get_prb_mask(config.bwp_start_rb, config.bwp_size_rb); @@ -75,199 +72,211 @@ void pdsch_processor_concurrent_impl::map(resource_grid_mapper& m // Calculate modulation scaling. float scaling = convert_dB_to_amplitude(-config.ratio_pdsch_data_to_sss_dB); - scaling *= cb_processor_pool.front()->get_scaling(config.codewords.front().modulation); + scaling *= cb_processor_pool->get().get_scaling(config.codewords.front().modulation); // Apply scaling to the precoding matrix. precoding_configuration precoding = config.precoding; precoding *= scaling; // Map into the resource grid. - mapper.map(buffer, allocation, reserved, precoding); + mapper->map(buffer, allocation, reserved, precoding); + + // Decrement asynchronous task counter. + if (async_task_counter.fetch_sub(1) == 1) { + // Notify end of the processing. + notifier->on_finish_processing(); + } } -void pdsch_processor_concurrent_impl::process(resource_grid_mapper& mapper, - static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, - const pdsch_processor::pdu_t& pdu) +void pdsch_processor_concurrent_impl::process(resource_grid_mapper& mapper_, + pdsch_processor_notifier& notifier_, + static_vector, MAX_NOF_TRANSPORT_BLOCKS> data_, + const pdsch_processor::pdu_t& pdu_) { - // Codeword index. - static constexpr unsigned i_cw = 0; + // Saves inputs. + save_inputs(mapper_, notifier_, data_, pdu_); - assert_pdu(pdu); + // Makes sure the PDU is valid. + assert_pdu(); - // The number of layers is equal to the number of ports. - unsigned nof_layers = pdu.precoding.get_nof_layers(); - - // Calculate the number of resource elements used to map PDSCH on the grid. Common for all codewords. - unsigned nof_re_pdsch = compute_nof_data_re(pdu); - - // Calculate scrambling initial state. - scrambler->init((static_cast(pdu.rnti) << 15U) + (i_cw << 14U) + pdu.n_id); - - // Select codeword specific parameters. - unsigned rv = pdu.codewords[i_cw].rv; - modulation_scheme modulation = pdu.codewords[i_cw].modulation; - - // Prepare segmenter configuration. - segmenter_config encoder_config; - encoder_config.base_graph = pdu.ldpc_base_graph; - encoder_config.rv = rv; - encoder_config.mod = modulation; - encoder_config.Nref = pdu.tbs_lbrm_bytes * 8; - encoder_config.nof_layers = nof_layers; - encoder_config.nof_ch_symbols = nof_re_pdsch * nof_layers; - - // Clear the buffer. - d_segments.clear(); - // Segmentation (it includes CRC attachment for the entire transport block and each individual segment). - segmenter->segment(d_segments, data[i_cw], encoder_config); + // Set the number of asynchronous tasks. It counts as CB processing and DM-RS generation. + async_task_counter = 2; - // Prepare data view to modulated symbols. - span codeword = span(temp_codeword).first(nof_layers * nof_re_pdsch); - - unsigned nof_cb = d_segments.size(); - - unsigned nof_cb_batches = std::min(nof_cb, static_cast(cb_processor_pool.size()) * 4); - unsigned cb_batch_size = divide_ceil(nof_cb, nof_cb_batches); + // Process DM-RS concurrently. + auto dmrs_task = [this]() { process_dmrs(); }; + if (!executor.execute(dmrs_task)) { + dmrs_task(); + } // Fork codeblock processing tasks. - unsigned cb_batch_count = 0; - for (unsigned i_cb_batch = 0, i_cb = 0; i_cb_batch != nof_cb_batches; ++i_cb_batch, i_cb += cb_batch_size) { - // Limit batch size for the last batch. - cb_batch_size = std::min(nof_cb - i_cb, cb_batch_size); - - // Extract scrambling initial state for the next bit. - pseudo_random_generator::state_s c_init = scrambler->get_state(); - - // Select segment description. - span segments = span(d_segments).subspan(i_cb, cb_batch_size); - - auto encode_task = [this, &cb_batch_count, segments, c_init, codeword]() mutable { - // Global count of threads. - static std::atomic global_count = {0}; - // Local thread index. - thread_local unsigned thread_index = global_count++; + fork_cb_batches(); +} - // Make sure the thread index does not exceed the codeblock processor pool. - srsran_assert(thread_index < cb_processor_pool.size(), - "Insufficient number of processors, i.e., {}, for thread index {}.", - cb_processor_pool.size(), - thread_index); +void pdsch_processor_concurrent_impl::save_inputs(resource_grid_mapper& mapper_, + pdsch_processor_notifier& notifier_, + static_vector, MAX_NOF_TRANSPORT_BLOCKS> data_, + const pdsch_processor::pdu_t& pdu) +{ + using namespace units::literals; - // Select codeblock processor. - pdsch_codeblock_processor& cb_processor = *cb_processor_pool[thread_index]; + // Save process parameter inputs. + mapper = &mapper_; + notifier = ¬ifier_; + data = data_.front(); + config = pdu; - // For each segment... - for (const described_segment& descr_seg : segments) { - // Process codeblock. - c_init = cb_processor.process(codeword, descr_seg, c_init); - } + // Codeword index is fix. + static constexpr unsigned i_cw = 0; - // Increment code block batch counter. - { - std::unique_lock lock(cb_count_mutex); - ++cb_batch_count; - cb_count_cvar.notify_all(); - } - }; + // The number of layers is equal to the number of ports. + unsigned nof_layers = config.precoding.get_nof_layers(); - // Try to execute task asynchronously. - bool successful = executor.execute(encode_task); + // Calculate the number of resource elements used to map PDSCH on the grid. Common for all codewords. + unsigned nof_re_pdsch = compute_nof_data_re(config); - // Increment code block batch counter if the task was not successfully executed. - if (!successful) { - std::unique_lock lock(cb_count_mutex); - ++cb_batch_count; - cb_count_cvar.notify_all(); - } + // Calculate the total number of the chanel modulated symbols. + nof_ch_symbols = nof_layers * nof_re_pdsch; - // Advance scrambling sequence for the next batch. - for (const described_segment& descr_seg : segments) { - scrambler->advance(descr_seg.get_metadata().cb_specific.rm_length); + // Calculate scrambling initial state. + scrambler->init((static_cast(config.rnti) << 15U) + (i_cw << 14U) + config.n_id); + + // Calculate transport block size. + tbs = units::bytes(data.size()).to_bits(); + + // Calculate number of codeblocks. + nof_cb = ldpc::compute_nof_codeblocks(tbs, config.ldpc_base_graph); + + // Number of segments that will have a short rate-matched length. In TS38.212 Section 5.4.2.1, these correspond to + // codeblocks whose length E_r is computed by rounding down - floor. For the remaining codewords, the length is + // rounded up. + unsigned nof_short_segments = nof_cb - (nof_re_pdsch % nof_cb); + + // Compute number of CRC bits for the transport block. + units::bits nof_tb_crc_bits = ldpc::compute_tb_crc_size(tbs); + + // Compute number of CRC bits for each codeblock. + units::bits nof_cb_crc_bits = (nof_cb > 1) ? 24_bits : 0_bits; + + // Calculate the total number of bits including transport block and codeblock CRC. + units::bits nof_tb_bits_out = tbs + nof_tb_crc_bits + units::bits(nof_cb_crc_bits * nof_cb); + + // Compute the number of information bits that is assigned to a codeblock. + cb_info_bits = units::bits(divide_ceil(nof_tb_bits_out.value(), nof_cb)) - nof_cb_crc_bits; + + unsigned lifting_size = ldpc::compute_lifting_size(tbs, config.ldpc_base_graph, nof_cb); + segment_length = ldpc::compute_codeblock_size(config.ldpc_base_graph, lifting_size); + + modulation_scheme modulation = config.codewords.front().modulation; + units::bits bits_per_symbol(get_bits_per_symbol(modulation)); + + units::bits full_codeblock_size = ldpc::compute_full_codeblock_size(config.ldpc_base_graph, segment_length); + units::bits cw_length = units::bits(nof_re_pdsch * nof_layers * bits_per_symbol); + zero_pad = (cb_info_bits + nof_cb_crc_bits) * nof_cb - nof_tb_bits_out; + + // Prepare codeblock metadata. + cb_metadata.tb_common.base_graph = config.ldpc_base_graph; + cb_metadata.tb_common.lifting_size = static_cast(lifting_size); + cb_metadata.tb_common.rv = config.codewords.front().rv; + cb_metadata.tb_common.mod = modulation; + cb_metadata.tb_common.Nref = units::bytes(config.tbs_lbrm_bytes).to_bits().value(); + cb_metadata.tb_common.cw_length = cw_length.value(); + cb_metadata.cb_specific.full_length = full_codeblock_size.value(); + cb_metadata.cb_specific.rm_length = 0; + cb_metadata.cb_specific.nof_filler_bits = (segment_length - cb_info_bits - nof_cb_crc_bits).value(); + cb_metadata.cb_specific.cw_offset = 0; + cb_metadata.cb_specific.nof_crc_bits = nof_tb_crc_bits.value(); + + // Calculate RM length for each codeblock. + rm_length.resize(nof_cb); + cw_offset.resize(nof_cb); + units::bits rm_length_sum = 0_bits; + for (unsigned i_cb = 0; i_cb != nof_cb; ++i_cb) { + // Calculate RM length in RE. + unsigned rm_length_re = divide_ceil(nof_re_pdsch, nof_cb); + if (i_cb < nof_short_segments) { + rm_length_re = nof_re_pdsch / nof_cb; } - } - // Process DM-RS while the codeblocks are being processed. - process_dmrs(mapper, pdu); + // Convert RM length from RE to bits. + rm_length[i_cb] = rm_length_re * nof_layers * bits_per_symbol; - // Wait for the asynchronous codeblock processing to finish. - { - std::unique_lock lock(cb_count_mutex); - cb_count_cvar.wait(lock, [&cb_batch_count, &nof_cb_batches]() { return cb_batch_count == nof_cb_batches; }); + // Set and increment CW offset. + cw_offset[i_cb] = rm_length_sum; + rm_length_sum += rm_length[i_cb]; } - - // Build resource grid mapper adaptor. - resource_grid_mapper::symbol_buffer_adapter buffer(codeword); - - // Map the process data. - map(mapper, buffer, pdu); + srsran_assert(rm_length_sum == cw_length, + "RM length sum (i.e., {}) must be equal to the codeword length (i.e., {}).", + rm_length_sum, + cw_length); } -void pdsch_processor_concurrent_impl::assert_pdu(const pdsch_processor::pdu_t& pdu) const +void pdsch_processor_concurrent_impl::assert_pdu() const { - // Deduce parameters from the PDU. - unsigned nof_layers = pdu.precoding.get_nof_layers(); - unsigned nof_symbols_slot = get_nsymb_per_slot(pdu.cp); - dmrs_config_type dmrs_config = (pdu.dmrs == dmrs_type::TYPE1) ? dmrs_config_type::type1 : dmrs_config_type::type2; + // Deduce parameters from the config. + unsigned nof_layers = config.precoding.get_nof_layers(); + unsigned nof_symbols_slot = get_nsymb_per_slot(config.cp); + dmrs_config_type dmrs_config = (config.dmrs == dmrs_type::TYPE1) ? dmrs_config_type::type1 : dmrs_config_type::type2; - srsran_assert(pdu.dmrs_symbol_mask.size() == nof_symbols_slot, + srsran_assert(config.dmrs_symbol_mask.size() == nof_symbols_slot, "The DM-RS symbol mask size (i.e., {}), must be equal to the number of symbols in the slot (i.e., {}).", - pdu.dmrs_symbol_mask.size(), + config.dmrs_symbol_mask.size(), nof_symbols_slot); - srsran_assert(pdu.dmrs_symbol_mask.any(), "The number of OFDM symbols carrying DM-RS RE must be greater than zero."); + srsran_assert(config.dmrs_symbol_mask.any(), + "The number of OFDM symbols carrying DM-RS RE must be greater than zero."); srsran_assert( - static_cast(pdu.dmrs_symbol_mask.find_lowest(true)) >= pdu.start_symbol_index, + static_cast(config.dmrs_symbol_mask.find_lowest(true)) >= config.start_symbol_index, "The index of the first OFDM symbol carrying DM-RS (i.e., {}) must be equal to or greater than the first symbol " "allocated to transmission (i.e., {}).", - pdu.dmrs_symbol_mask.find_lowest(true), - pdu.start_symbol_index); - srsran_assert(static_cast(pdu.dmrs_symbol_mask.find_highest(true)) < - (pdu.start_symbol_index + pdu.nof_symbols), + config.dmrs_symbol_mask.find_lowest(true), + config.start_symbol_index); + srsran_assert(static_cast(config.dmrs_symbol_mask.find_highest(true)) < + (config.start_symbol_index + config.nof_symbols), "The index of the last OFDM symbol carrying DM-RS (i.e., {}) must be less than or equal to the last " "symbol allocated to transmission (i.e., {}).", - pdu.dmrs_symbol_mask.find_highest(true), - pdu.start_symbol_index + pdu.nof_symbols - 1); - srsran_assert((pdu.start_symbol_index + pdu.nof_symbols) <= nof_symbols_slot, + config.dmrs_symbol_mask.find_highest(true), + config.start_symbol_index + config.nof_symbols - 1); + srsran_assert((config.start_symbol_index + config.nof_symbols) <= nof_symbols_slot, "The transmission with time allocation [{}, {}) exceeds the slot boundary of {} symbols.", - pdu.start_symbol_index, - pdu.start_symbol_index + pdu.nof_symbols, + config.start_symbol_index, + config.start_symbol_index + config.nof_symbols, nof_symbols_slot); - srsran_assert(pdu.freq_alloc.is_bwp_valid(pdu.bwp_start_rb, pdu.bwp_size_rb), + srsran_assert(config.freq_alloc.is_bwp_valid(config.bwp_start_rb, config.bwp_size_rb), "Invalid BWP configuration [{}, {}) for the given frequency allocation {}.", - pdu.bwp_start_rb, - pdu.bwp_start_rb + pdu.bwp_size_rb, - pdu.freq_alloc); - srsran_assert(pdu.dmrs == dmrs_type::TYPE1, "Only DM-RS Type 1 is currently supported."); - srsran_assert(pdu.freq_alloc.is_contiguous(), "Only contiguous allocation is currently supported."); - srsran_assert(pdu.nof_cdm_groups_without_data <= get_max_nof_cdm_groups_without_data(dmrs_config), + config.bwp_start_rb, + config.bwp_start_rb + config.bwp_size_rb, + config.freq_alloc); + srsran_assert(config.dmrs == dmrs_type::TYPE1, "Only DM-RS Type 1 is currently supported."); + srsran_assert(config.freq_alloc.is_contiguous(), "Only contiguous allocation is currently supported."); + srsran_assert(config.nof_cdm_groups_without_data <= get_max_nof_cdm_groups_without_data(dmrs_config), "The number of CDM groups without data (i.e., {}) must not exceed the maximum supported by the DM-RS " "type (i.e., {}).", - pdu.nof_cdm_groups_without_data, + config.nof_cdm_groups_without_data, get_max_nof_cdm_groups_without_data(dmrs_config)); srsran_assert(nof_layers != 0, "No transmit layers are active."); srsran_assert(nof_layers <= 4, "Only 1 to 4 layers are currently supported. {} layers requested.", nof_layers); - srsran_assert(pdu.codewords.size() == 1, "Only one codeword is currently supported."); - srsran_assert(pdu.tbs_lbrm_bytes > 0 && pdu.tbs_lbrm_bytes <= ldpc::MAX_CODEBLOCK_SIZE / 8, + srsran_assert(config.codewords.size() == 1, "Only one codeword is currently supported."); + srsran_assert(config.tbs_lbrm_bytes > 0 && config.tbs_lbrm_bytes <= ldpc::MAX_CODEBLOCK_SIZE / 8, "Invalid LBRM size ({} bytes). It must be non-zero, less than or equal to {} bytes", - pdu.tbs_lbrm_bytes, + config.tbs_lbrm_bytes, ldpc::MAX_CODEBLOCK_SIZE / 8); } -unsigned pdsch_processor_concurrent_impl::compute_nof_data_re(const pdu_t& pdu) +unsigned pdsch_processor_concurrent_impl::compute_nof_data_re(const pdu_t& config) { // Copy reserved RE and merge DMRS pattern. - re_pattern_list reserved_re = pdu.reserved; - reserved_re.merge(pdu.dmrs.get_dmrs_pattern( - pdu.bwp_start_rb, pdu.bwp_size_rb, pdu.nof_cdm_groups_without_data, pdu.dmrs_symbol_mask)); + re_pattern_list reserved_re = config.reserved; + reserved_re.merge(config.dmrs.get_dmrs_pattern( + config.bwp_start_rb, config.bwp_size_rb, config.nof_cdm_groups_without_data, config.dmrs_symbol_mask)); // Generate allocation mask. - bounded_bitset prb_mask = pdu.freq_alloc.get_prb_mask(pdu.bwp_start_rb, pdu.bwp_size_rb); + bounded_bitset prb_mask = config.freq_alloc.get_prb_mask(config.bwp_start_rb, config.bwp_size_rb); // Calculate the number of RE allocated in the grid. - unsigned nof_grid_re = pdu.freq_alloc.get_nof_rb() * NRE * pdu.nof_symbols; + unsigned nof_grid_re = config.freq_alloc.get_nof_rb() * NRE * config.nof_symbols; // Calculate the number of reserved resource elements. - unsigned nof_reserved_re = reserved_re.get_inclusion_count(pdu.start_symbol_index, pdu.nof_symbols, prb_mask); + unsigned nof_reserved_re = reserved_re.get_inclusion_count(config.start_symbol_index, config.nof_symbols, prb_mask); // Subtract the number of reserved RE from the number of allocated RE. srsran_assert(nof_grid_re > nof_reserved_re, @@ -277,28 +286,119 @@ unsigned pdsch_processor_concurrent_impl::compute_nof_data_re(const pdu_t& pdu) return nof_grid_re - nof_reserved_re; } -void pdsch_processor_concurrent_impl::process_dmrs(resource_grid_mapper& mapper, const pdu_t& pdu) +void pdsch_processor_concurrent_impl::fork_cb_batches() +{ + // Prepare data view to modulated symbols. + span codeword = span(temp_codeword).first(nof_ch_symbols); + + // Minimum number of codeblocks per batch. + unsigned min_cb_batch_size = 4; + + // Calculate the number of batches. + unsigned nof_cb_batches = cb_processor_pool->capacity() * 8; + + // Limit the number of batches to ensure a minimum number of CB per batch. + unsigned max_nof_cb_batches = divide_ceil(nof_cb, min_cb_batch_size); + nof_cb_batches = std::min(nof_cb_batches, max_nof_cb_batches); + + // Calculate the actual number of CB per batch. + unsigned cb_batch_size = divide_ceil(nof_cb, nof_cb_batches); + + // Set number of codeblock batches. + cb_batch_counter = nof_cb_batches; + + for (unsigned i_cb_batch = 0, i_cb = 0; i_cb_batch != nof_cb_batches; ++i_cb_batch, i_cb += cb_batch_size) { + // Limit batch size for the last batch. + cb_batch_size = std::min(nof_cb - i_cb, cb_batch_size); + + // Extract scrambling initial state for the next bit. + pseudo_random_generator::state_s c_init = scrambler->get_state(); + + auto async_task = [this, cb_batch_size, c_init, codeword, i_cb]() { + // Select codeblock processor. + pdsch_codeblock_processor& cb_processor = cb_processor_pool->get(); + + // Save scrambling initial state. + pseudo_random_generator::state_s scrambling_state = c_init; + + // For each segment... + for (unsigned batch_i_cb = 0; batch_i_cb != cb_batch_size; ++batch_i_cb) { + // Calculate the absolute codeblock index. + unsigned absolute_i_cb = i_cb + batch_i_cb; + + // Limit the codeblock number of information bits. + units::bits nof_info_bits = std::min(cb_info_bits, tbs - cb_info_bits * absolute_i_cb); + + // Set CB processor configuration. + pdsch_codeblock_processor::configuration cb_config; + cb_config.tb_offset = cb_info_bits * absolute_i_cb; + cb_config.has_cb_crc = nof_cb > 1; + cb_config.cb_info_size = nof_info_bits; + cb_config.cb_size = segment_length; + cb_config.zero_pad = zero_pad; + cb_config.metadata = cb_metadata; + cb_config.c_init = scrambling_state; + + // Update codeblock specific metadata fields. + cb_config.metadata.cb_specific.cw_offset = cw_offset[absolute_i_cb].value(); + cb_config.metadata.cb_specific.rm_length = rm_length[absolute_i_cb].value(); + + // Process codeblock. + scrambling_state = cb_processor.process(codeword, data, cb_config); + } + + // Decrement code block batch counter. + if (cb_batch_counter.fetch_sub(1) == 1) { + map(codeword); + } + }; + + // Try to execute task asynchronously. + bool successful = false; + if (nof_cb_batches != 0) { + successful = executor.execute(async_task); + } + + // Execute task locally if it was not enqueued. + if (!successful) { + async_task(); + } + + // Advance scrambling sequence for the next batch. + for (unsigned i = 0; i != cb_batch_size; ++i) { + scrambler->advance(rm_length[i_cb + i].value()); + } + } +} + +void pdsch_processor_concurrent_impl::process_dmrs() { - bounded_bitset rb_mask_bitset = pdu.freq_alloc.get_prb_mask(pdu.bwp_start_rb, pdu.bwp_size_rb); + bounded_bitset rb_mask_bitset = config.freq_alloc.get_prb_mask(config.bwp_start_rb, config.bwp_size_rb); // Select the DM-RS reference point. unsigned dmrs_reference_point_k_rb = 0; - if (pdu.ref_point == pdu_t::PRB0) { - dmrs_reference_point_k_rb = pdu.bwp_start_rb; + if (config.ref_point == pdu_t::PRB0) { + dmrs_reference_point_k_rb = config.bwp_start_rb; } // Prepare DM-RS configuration. dmrs_pdsch_processor::config_t dmrs_config; - dmrs_config.slot = pdu.slot; + dmrs_config.slot = config.slot; dmrs_config.reference_point_k_rb = dmrs_reference_point_k_rb; - dmrs_config.type = pdu.dmrs; - dmrs_config.scrambling_id = pdu.scrambling_id; - dmrs_config.n_scid = pdu.n_scid; - dmrs_config.amplitude = convert_dB_to_amplitude(-pdu.ratio_pdsch_dmrs_to_sss_dB); - dmrs_config.symbols_mask = pdu.dmrs_symbol_mask; + dmrs_config.type = config.dmrs; + dmrs_config.scrambling_id = config.scrambling_id; + dmrs_config.n_scid = config.n_scid; + dmrs_config.amplitude = convert_dB_to_amplitude(-config.ratio_pdsch_dmrs_to_sss_dB); + dmrs_config.symbols_mask = config.dmrs_symbol_mask; dmrs_config.rb_mask = rb_mask_bitset; - dmrs_config.precoding = pdu.precoding; + dmrs_config.precoding = config.precoding; // Put DM-RS. - dmrs->map(mapper, dmrs_config); -} \ No newline at end of file + dmrs->map(*mapper, dmrs_config); + + // Decrement asynchronous task counter. + if (async_task_counter.fetch_sub(1) == 1) { + // Notify end of the processing. + notifier->on_finish_processing(); + } +} diff --git a/lib/phy/upper/channel_processors/pdsch_processor_concurrent_impl.h b/lib/phy/upper/channel_processors/pdsch_processor_concurrent_impl.h index b757dc574c..9c25b740c4 100644 --- a/lib/phy/upper/channel_processors/pdsch_processor_concurrent_impl.h +++ b/lib/phy/upper/channel_processors/pdsch_processor_concurrent_impl.h @@ -22,19 +22,12 @@ #pragma once #include "pdsch_codeblock_processor.h" -#include "srsran/phy/support/re_buffer.h" #include "srsran/phy/support/resource_grid_mapper.h" -#include "srsran/phy/upper/channel_coding/ldpc/ldpc_segmenter_tx.h" -#include "srsran/phy/upper/channel_modulation/modulation_mapper.h" -#include "srsran/phy/upper/channel_processors/pdsch_encoder.h" #include "srsran/phy/upper/channel_processors/pdsch_processor.h" #include "srsran/phy/upper/sequence_generators/pseudo_random_generator.h" #include "srsran/phy/upper/signal_processors/dmrs_pdsch_processor.h" -#include "srsran/ran/pdsch/pdsch_constants.h" -#include "srsran/srsvec/bit.h" #include "srsran/support/executors/task_executor.h" -#include -#include +#include "srsran/support/memory_pool/concurrent_thread_local_object_pool.h" namespace srsran { @@ -44,34 +37,33 @@ namespace srsran { class pdsch_processor_concurrent_impl : public pdsch_processor { public: + /// Codeblock processor pool type. + using codeblock_processor_pool = concurrent_thread_local_object_pool; + /// \brief Creates a concurrent PDSCH processor with all the dependencies. /// \param[in] segmenter_ LDPC transmitter segmenter. - /// \param[in] cb_processor_pool_ Codeblock processor pool, one instance per thread. + /// \param[in] cb_processor_pool_ Codeblock processor pool. /// \param[in] scrambler_ Scrambling pseudo-random generator. /// \param[in] dmrs_ DM-RS for PDSCH generator. /// \param[in] executor_ Asynchronous task executor. - pdsch_processor_concurrent_impl(std::unique_ptr segmenter_, - std::vector> cb_processor_pool_, - std::unique_ptr scrambler_, - std::unique_ptr dmrs_, - task_executor& executor_) : - segmenter(std::move(segmenter_)), + pdsch_processor_concurrent_impl(std::shared_ptr cb_processor_pool_, + std::unique_ptr scrambler_, + std::unique_ptr dmrs_, + task_executor& executor_) : scrambler(std::move(scrambler_)), cb_processor_pool(std::move(cb_processor_pool_)), dmrs(std::move(dmrs_)), executor(executor_), temp_codeword(pdsch_constants::CODEWORD_MAX_SYMBOLS) { - srsran_assert(segmenter != nullptr, "Invalid segmenter pointer."); - srsran_assert(!cb_processor_pool.empty(), "CB processor pool is empty."); - srsran_assert(std::find(cb_processor_pool.begin(), cb_processor_pool.end(), nullptr) == cb_processor_pool.end(), - "Invalid CB processor in pool."); srsran_assert(scrambler != nullptr, "Invalid scrambler pointer."); - srsran_assert(dmrs != nullptr, "Invalid dmrs pointer."); + srsran_assert(cb_processor_pool != nullptr, "Invalid CB processor pool pointer."); + srsran_assert(dmrs != nullptr, "Invalid DM-RS pointer."); } // See interface for documentation. void process(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier, static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, const pdu_t& pdu) override; @@ -84,42 +76,67 @@ class pdsch_processor_concurrent_impl : public pdsch_processor /// \return The number of resource elements. static unsigned compute_nof_data_re(const pdu_t& pdu); + /// Saves process() parameters for future uses during an asynchronous execution. + void save_inputs(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier, + static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, + const pdu_t& pdu); + /// \brief Asserts PDU. /// /// It triggers an assertion if the PDU is not valid for this processor. - void assert_pdu(const pdu_t& pdu) const; + void assert_pdu() const; + + /// Creates code block processing batches and starts the asynchronous processing. + void fork_cb_batches(); - /// \brief Processes PDSCH DM-RS. - /// \param[out] mapper Resource grid mapper interface. - /// \param[in] pdu Necessary parameters to process the PDSCH transmission. - void process_dmrs(resource_grid_mapper& mapper, const pdu_t& pdu); + /// Processes PDSCH DM-RS. + void process_dmrs(); /// \brief Maps the PDSCH resource elements. - /// \param[out] mapper Resource grid mapper interface. /// \param[in] buffer Symbols after modulation mapping. /// \param[in] config Necessary parameters to process the PDSCH transmission. - void map(resource_grid_mapper& mapper, resource_grid_mapper::symbol_buffer& buffer, const pdu_t& config); + void map(span codeword); - /// Pointer to an LDPC segmenter. - std::unique_ptr segmenter; /// Pseudo-random generator. std::unique_ptr scrambler; /// Pool of code block processors. - std::vector> cb_processor_pool; + std::shared_ptr cb_processor_pool; /// DM-RS processor. std::unique_ptr dmrs; /// Asynchronous task executor. task_executor& executor; - /// Buffer for storing data segments obtained after transport block segmentation. - static_vector d_segments = {}; + resource_grid_mapper* mapper; + pdsch_processor_notifier* notifier; + span data; + pdsch_processor::pdu_t config; + + /// Transport block size of the current transmission. + units::bits tbs; + /// Number of codeblocks of the current transmission. + unsigned nof_cb = 0; + /// Number of modulated channel symbols. + unsigned nof_ch_symbols = 0; + /// Number of information bits per codeblock. + units::bits cb_info_bits = units::bits(0); + /// LDPC segment length. + units::bits segment_length = units::bits(0); + /// Number of zeros that must be applied to the transport block. + units::bits zero_pad = units::bits(0); + /// Base codeblock metadata. + codeblock_metadata cb_metadata = {}; + + /// Rate matching length in bits for each of the segments. + static_vector rm_length; + /// Codeblock bit offset within the codeword. + static_vector cw_offset; /// Buffer for storing the modulated codeword. std::vector temp_codeword; - - /// Mutex for protecting code block counter. - std::mutex cb_count_mutex; - /// Condition variable for notifying the codeblock count change. - std::condition_variable cb_count_cvar; + /// Pending code block batch counter. + std::atomic cb_batch_counter; + /// Pending asynchronous task counter (DM-RS and CB processing). + std::atomic async_task_counter; }; } // namespace srsran diff --git a/lib/phy/upper/channel_processors/pdsch_processor_impl.cpp b/lib/phy/upper/channel_processors/pdsch_processor_impl.cpp index 05677f1799..7280826c58 100644 --- a/lib/phy/upper/channel_processors/pdsch_processor_impl.cpp +++ b/lib/phy/upper/channel_processors/pdsch_processor_impl.cpp @@ -105,6 +105,7 @@ bool pdsch_processor_validator_impl::is_valid(const pdsch_processor::pdu_t& pdu) } void pdsch_processor_impl::process(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier, static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, const pdsch_processor::pdu_t& pdu) { @@ -143,6 +144,9 @@ void pdsch_processor_impl::process(resource_grid_mapper& // Prepare DM-RS configuration and generate. put_dmrs(mapper, pdu); + + // Notify the end of the processing. + notifier.on_finish_processing(); } void pdsch_processor_impl::assert_pdu(const pdsch_processor::pdu_t& pdu) const diff --git a/lib/phy/upper/channel_processors/pdsch_processor_impl.h b/lib/phy/upper/channel_processors/pdsch_processor_impl.h index 4f26004dfd..d69fb5f41d 100644 --- a/lib/phy/upper/channel_processors/pdsch_processor_impl.h +++ b/lib/phy/upper/channel_processors/pdsch_processor_impl.h @@ -55,6 +55,7 @@ class pdsch_processor_impl : public pdsch_processor // See interface for documentation. void process(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier, static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, const pdu_t& pdu) override; diff --git a/lib/phy/upper/channel_processors/pdsch_processor_lite_impl.cpp b/lib/phy/upper/channel_processors/pdsch_processor_lite_impl.cpp index acc26c1d9b..f24f770f5d 100644 --- a/lib/phy/upper/channel_processors/pdsch_processor_lite_impl.cpp +++ b/lib/phy/upper/channel_processors/pdsch_processor_lite_impl.cpp @@ -23,7 +23,6 @@ #include "pdsch_processor_lite_impl.h" #include "srsran/phy/support/resource_grid_mapper.h" #include "srsran/ran/dmrs.h" -#include "srsran/srsvec/sc_prod.h" using namespace srsran; @@ -96,8 +95,7 @@ void pdsch_block_processor::configure_new_transmission(span void pdsch_block_processor::new_codeblock() { // Temporary data storage. - std::array temp_unpacked_cb; - std::array buffer_cb; + static_bit_buffer<3 * MAX_SEG_LENGTH.value()> rm_buffer; srsran_assert(next_i_cb < d_segments.size(), "The codeblock index (i.e., {}) exceeds the number of codeblocks (i.e., {})", @@ -107,34 +105,21 @@ void pdsch_block_processor::new_codeblock() // Select segment description. const described_segment& descr_seg = d_segments[next_i_cb]; - // CB payload number of bits. - unsigned cb_length = descr_seg.get_data().size(); - // Rate Matching output length. unsigned rm_length = descr_seg.get_metadata().cb_specific.rm_length; // Number of symbols. unsigned nof_symbols = rm_length / get_bits_per_symbol(modulation); - // Resize internal buffer to match data from the segmenter to the encoder (all segments have the same length). - span tmp_data = span(temp_unpacked_cb).first(cb_length); - // Resize internal buffer to match data from the encoder to the rate matcher (all segments have the same length). - span tmp_encoded = span(buffer_cb).first(descr_seg.get_metadata().cb_specific.full_length); - - // Unpack segment. - srsvec::bit_unpack(tmp_data, descr_seg.get_data()); - - // Set filler bits. - span filler_bits = tmp_data.last(descr_seg.get_metadata().cb_specific.nof_filler_bits); - std::fill(filler_bits.begin(), filler_bits.end(), ldpc::FILLER_BIT); + rm_buffer.resize(descr_seg.get_metadata().cb_specific.full_length); // Encode the segment into a codeblock. - encoder.encode(tmp_encoded, tmp_data, descr_seg.get_metadata().tb_common); + encoder.encode(rm_buffer, descr_seg.get_data(), descr_seg.get_metadata().tb_common); // Rate match the codeblock. temp_codeblock.resize(rm_length); - rate_matcher.rate_match(temp_codeblock, tmp_encoded, descr_seg.get_metadata()); + rate_matcher.rate_match(temp_codeblock, rm_buffer, descr_seg.get_metadata()); // Apply scrambling sequence in-place. scrambler.apply_xor(temp_codeblock, temp_codeblock); @@ -194,6 +179,7 @@ span pdsch_block_processor::pop_symbols(unsigned block_size) } void pdsch_processor_lite_impl::process(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier, static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, const pdsch_processor::pdu_t& pdu) { @@ -254,6 +240,9 @@ void pdsch_processor_lite_impl::process(resource_grid_mapper& // Process DM-RS. process_dmrs(mapper, pdu); + + // Notify the end of the processing. + notifier.on_finish_processing(); } void pdsch_processor_lite_impl::assert_pdu(const pdsch_processor::pdu_t& pdu) const diff --git a/lib/phy/upper/channel_processors/pdsch_processor_lite_impl.h b/lib/phy/upper/channel_processors/pdsch_processor_lite_impl.h index 490bb5c925..f90bec961b 100644 --- a/lib/phy/upper/channel_processors/pdsch_processor_lite_impl.h +++ b/lib/phy/upper/channel_processors/pdsch_processor_lite_impl.h @@ -122,6 +122,7 @@ class pdsch_processor_lite_impl : public pdsch_processor // See interface for documentation. void process(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier, static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, const pdu_t& pdu) override; diff --git a/lib/phy/upper/channel_processors/pdsch_processor_pool.h b/lib/phy/upper/channel_processors/pdsch_processor_pool.h new file mode 100644 index 0000000000..296e85a104 --- /dev/null +++ b/lib/phy/upper/channel_processors/pdsch_processor_pool.h @@ -0,0 +1,137 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/concurrent_queue.h" +#include "srsran/adt/ring_buffer.h" +#include "srsran/phy/upper/channel_processors/channel_processor_formatters.h" +#include "srsran/phy/upper/channel_processors/pdsch_processor.h" +#include "srsran/srslog/logger.h" + +namespace srsran { + +namespace detail { + +/// Free PDSCH processor identifier list type. +using pdsch_processor_free_list = + concurrent_queue; + +/// PDSCH processor wrapper. It appends its identifier into the free list when the processing is finished. +class pdsch_processor_wrapper : private pdsch_processor, private pdsch_processor_notifier +{ +public: + /// Creates a PDSCH processor wrapper from another PDSCH processor. + explicit pdsch_processor_wrapper(unsigned index_, + pdsch_processor_free_list& free_list_, + std::unique_ptr processor_) : + index(index_), free_list(free_list_), notifier(nullptr), processor(std::move(processor_)) + { + srsran_assert(processor, "Invalid PDSCH processor."); + } + + /// Creates a PDSCH processor wrapper from another PDSCH processor wrapper. + pdsch_processor_wrapper(pdsch_processor_wrapper&& other) : + index(other.index), free_list(other.free_list), notifier(other.notifier), processor(std::move(other.processor)) + { + other.notifier = nullptr; + } + + // See pdsch_processor interface for documentation. + void process(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier_, + static_vector, pdsch_processor::MAX_NOF_TRANSPORT_BLOCKS> data, + const pdsch_processor::pdu_t& pdu) override + { + // Save original notifier. + notifier = ¬ifier_; + + // Process. + processor->process(mapper, *this, data, pdu); + } + +private: + // See pdsch_processor_notifier for documentation. + void on_finish_processing() override + { + srsran_assert(notifier != nullptr, "Invalid notifier."); + + // Notify the completion of the processing. + notifier->on_finish_processing(); + + // Return the PDSCH processor identifier to the free list. + free_list.push_blocking(index); + } + + /// Processor identifier within the pool. + unsigned index; + /// List of free processors. + pdsch_processor_free_list& free_list; + /// Current PDSCH processor notifier. + pdsch_processor_notifier* notifier = nullptr; + /// Wrapped PDSCH processor. + std::unique_ptr processor; +}; +} // namespace detail + +/// \brief PDSCH processor pool +/// +/// It contains PDSCH processors that are asynchronously executed. The processing of a PDSCH transmission is dropped if +/// there are no free PDSCH processors available. +/// +class pdsch_processor_pool : public pdsch_processor +{ +public: + explicit pdsch_processor_pool(span> processors_) : free_list(processors_.size()) + { + unsigned index = 0; + for (std::unique_ptr& processor : processors_) { + free_list.push_blocking(index); + processors.emplace_back(detail::pdsch_processor_wrapper(index++, free_list, std::move(processor))); + } + } + + void process(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier, + static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, + const pdu_t& pdu) override + { + // Try to get a worker. + optional index = free_list.try_pop(); + + // If no worker is available. + if (!index.has_value()) { + srslog::fetch_basic_logger("PHY").warning("Insufficient number of PDSCH processors. Dropping PDSCH {:s}.", pdu); + notifier.on_finish_processing(); + return; + } + + // Process PDSCH. + processors[index.value()].process(mapper, notifier, data, pdu); + } + +private: + std::vector processors; + detail::pdsch_processor_free_list free_list; +}; + +} // namespace srsran \ No newline at end of file diff --git a/lib/phy/upper/channel_processors/pusch/CMakeLists.txt b/lib/phy/upper/channel_processors/pusch/CMakeLists.txt index ceedef2a7a..40d89508fc 100644 --- a/lib/phy/upper/channel_processors/pusch/CMakeLists.txt +++ b/lib/phy/upper/channel_processors/pusch/CMakeLists.txt @@ -18,12 +18,10 @@ # and at http://www.gnu.org/licenses/. # -add_library(srsran_pusch_decoder STATIC pusch_decoder_impl.cpp) -target_link_libraries(srsran_pusch_decoder srsran_channel_coding srsvec) - -add_library(srsran_pusch_demodulator STATIC pusch_demodulator_impl.cpp) - -add_library(srsran_pusch_processor STATIC pusch_processor_impl.cpp) +add_library(srsran_pusch_processor STATIC + pusch_codeblock_decoder.cpp + pusch_decoder_impl.cpp + pusch_demodulator_impl.cpp + pusch_processor_impl.cpp + ulsch_demultiplex_impl.cpp) target_link_libraries(srsran_pusch_processor srsran_upper_phy_support srsran_ran) - -add_library(srsran_ulsch_demux STATIC ulsch_demultiplex_impl.cpp) diff --git a/lib/phy/upper/channel_processors/pusch/pusch_codeblock_decoder.cpp b/lib/phy/upper/channel_processors/pusch/pusch_codeblock_decoder.cpp new file mode 100644 index 0000000000..39ff77d39a --- /dev/null +++ b/lib/phy/upper/channel_processors/pusch/pusch_codeblock_decoder.cpp @@ -0,0 +1,71 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "pusch_codeblock_decoder.h" + +using namespace srsran; + +void pusch_codeblock_decoder::rate_match(span rm_buffer, + span cb_llrs, + bool new_data, + const codeblock_metadata& metadata) +{ + dematcher->rate_dematch(rm_buffer, cb_llrs, new_data, metadata); +} + +optional pusch_codeblock_decoder::decode(bit_buffer cb_data, + span rm_buffer, + span cb_llrs, + bool new_data, + srsran::crc_generator_poly crc_poly, + bool use_early_stop, + unsigned nof_ldpc_iterations, + const codeblock_metadata& metadata) +{ + rate_match(rm_buffer, cb_llrs, new_data, metadata); + + // Prepare LDPC decoder configuration. + ldpc_decoder::configuration decoder_config; + decoder_config.block_conf = metadata; + decoder_config.algorithm_conf.max_iterations = nof_ldpc_iterations; + // As for the other algorithm_details, we use the default values. + + // Select CRC calculator. + crc_calculator* crc = select_crc(crc_poly); + srsran_assert(crc != nullptr, "Invalid CRC calculator."); + + // Decode with early stop. + if (use_early_stop) { + return decoder->decode(cb_data, rm_buffer, crc, decoder_config); + } + + // Without early stop, first decode and then check the CRC. + decoder->decode(cb_data, rm_buffer, nullptr, decoder_config); + + // Discard filler bits for the CRC. + unsigned nof_significant_bits = cb_data.size() - metadata.cb_specific.nof_filler_bits; + if (crc->calculate(cb_data.first(nof_significant_bits)) == 0) { + return nof_ldpc_iterations; + } + + return nullopt; +} \ No newline at end of file diff --git a/lib/phy/upper/channel_processors/pusch/pusch_codeblock_decoder.h b/lib/phy/upper/channel_processors/pusch/pusch_codeblock_decoder.h new file mode 100644 index 0000000000..92372074ee --- /dev/null +++ b/lib/phy/upper/channel_processors/pusch/pusch_codeblock_decoder.h @@ -0,0 +1,138 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/phy/upper/channel_coding/crc_calculator.h" +#include "srsran/phy/upper/channel_coding/ldpc/ldpc_decoder.h" +#include "srsran/phy/upper/channel_coding/ldpc/ldpc_rate_dematcher.h" +#include "srsran/phy/upper/channel_coding/ldpc/ldpc_segmenter_rx.h" +#include "srsran/phy/upper/channel_processors/pusch/pusch_decoder.h" +#include "srsran/ran/pusch/pusch_constants.h" +#include + +namespace srsran { + +/// \brief PUSCH code block decoder. +/// +/// Reverts the rate matching, LDPC encoding and CRC check. +class pusch_codeblock_decoder +{ +public: + /// CRC calculators used in shared channels. + struct sch_crc { + /// For short TB checksums. + std::unique_ptr crc16; + /// For long TB checksums. + std::unique_ptr crc24A; + /// For segment-specific checksums. + std::unique_ptr crc24B; + }; + + /// \brief PUSCH code block decoder constructor. + /// + /// Sets up the internal components, namely LDPC rate dematcher, LDPC decoder and all the CRC calculators. + /// + /// \param[in] rdem Pointer to an LDPC rate dematcher object. + /// \param[in] dec Pointer to an LDPC decoder object. + /// \param[in] crcs Structure with pointers to three CRC calculator objects, with generator polynomials of type \c + /// CRC16, \c CRC24A and \c CRC24B. + pusch_codeblock_decoder(std::unique_ptr rdem, std::unique_ptr dec, sch_crc& crcs) : + dematcher(std::move(rdem)), + decoder(std::move(dec)), + crc_set({std::move(crcs.crc16), std::move(crcs.crc24A), std::move(crcs.crc24B)}) + { + srsran_assert(dematcher, "Invalid dematcher."); + srsran_assert(crc_set.crc16, "Invalid CRC16 calculator."); + srsran_assert(crc_set.crc24A, "Invalid CRC24A calculator."); + srsran_assert(crc_set.crc24B, "Invalid CRC24B calculator."); + srsran_assert(crc_set.crc16->get_generator_poly() == crc_generator_poly::CRC16, "Wrong TB CRC calculator."); + srsran_assert(crc_set.crc24A->get_generator_poly() == crc_generator_poly::CRC24A, "Wrong TB CRC calculator."); + srsran_assert(crc_set.crc24B->get_generator_poly() == crc_generator_poly::CRC24B, "Wrong TB CRC calculator."); + }; + + /// Selects the CRC calculator from a CRC polynomial. + crc_calculator* select_crc(crc_generator_poly poly) + { + srsran_assert((poly == crc_generator_poly::CRC16) || (poly == crc_generator_poly::CRC24A) || + (poly == crc_generator_poly::CRC24B), + "Invalid CRC polynomial."); + if (poly == crc_generator_poly::CRC16) { + return crc_set.crc16.get(); + } + if (poly == crc_generator_poly::CRC24A) { + return crc_set.crc24A.get(); + } + if (poly == crc_generator_poly::CRC24B) { + return crc_set.crc24B.get(); + } + return nullptr; + } + + /// \brief Rate matches. + /// + /// Reverts the rate matching process. + /// + /// \param[in,out] rm_buffer Rate matching buffer. + /// \param[in] cb_llrs New soft bits to write in the soft buffer. + /// \param[in] new_data Set to true for indicating a new transmission. + /// \param[in] metadata Code block metadata. + void rate_match(span rm_buffer, + span cb_llrs, + bool new_data, + const codeblock_metadata& metadata); + + /// \brief Rate matches. + /// + /// Reverts the rate matching process and applies LDPC decoding. + /// + /// \param[out] cb_data Code block data after decoding. + /// \param[in,out] rm_buffer Rate matching buffer. + /// \param[in] cb_llrs New soft bits to write in the soft buffer. + /// \param[in] new_data Set to true for indicating a new transmission. + /// \param[in] crc_poly CRC polynomial used for the code block. + /// \param[in] use_early_stop Set to true to allow the LDPC decoder to stop decoding when the CRC matches. + /// \param[in] nof_ldpc_iterations Number of LDPC decoder iterations. + /// \param[in] metadata Code block metadata. + /// \return The number of iterations if the CRC matches after the LDPC decoder. Otherwise, \c nullopt. + optional decode(bit_buffer cb_data, + span rm_buffer, + span cb_llrs, + bool new_data, + srsran::crc_generator_poly crc_poly, + bool use_early_stop, + unsigned nof_ldpc_iterations, + const codeblock_metadata& metadata); + +private: + /// Pointer to an LDPC rate-dematcher. + std::unique_ptr dematcher; + /// Pointer to an LDPC decoder. + std::unique_ptr decoder; + /// \brief Pointer to a CRC calculator for TB-wise checksum. + /// + /// Only the CRC calculator with generator polynomial crc_generator_poly::CRC24A, used for long transport blocks, is + /// needed. Indeed, if a transport block is short enough not to be segmented, the CRC is verified by the decoder. + sch_crc crc_set; +}; + +} // namespace srsran diff --git a/lib/phy/upper/channel_processors/pusch/pusch_decoder_impl.cpp b/lib/phy/upper/channel_processors/pusch/pusch_decoder_impl.cpp index b5787938bf..77716d80e5 100644 --- a/lib/phy/upper/channel_processors/pusch/pusch_decoder_impl.cpp +++ b/lib/phy/upper/channel_processors/pusch/pusch_decoder_impl.cpp @@ -30,19 +30,6 @@ using namespace srsran; -// Number of bits in one byte. -static constexpr unsigned BITS_PER_BYTE = 8; - -// Maximum TBS that implies a 16-bit CRC. -constexpr unsigned MAX_BITS_CRC16 = 3824; - -// Number of bits in the long CRC. A CRC of this length is used either for TB CRCs, when the TB is longer than -// MAX_BITS_CRC16, or as a codeblock CRC, when the TB consists of multiple codeblocks. -constexpr unsigned LONG_CRC_LENGTH = 24; - -// Maximum accepted transport block size. -static constexpr unsigned MAX_TBS = 1277992; - // Select the CRC for the decoder based on the TBS and the number of codeblocks. crc_calculator* select_crc(pusch_decoder_impl::sch_crc& crcs, unsigned tbs, unsigned nof_blocks) { @@ -57,21 +44,6 @@ crc_calculator* select_crc(pusch_decoder_impl::sch_crc& crcs, unsigned tbs, unsi return crcs.crc16.get(); } -// Computes the TB size in bits including the CRC. The CRC is accounted for only when there are multiple codeblocks. -// Otherwise, one the TB consists of a single codeblock, tb_and_crc_size = tb_size. The input is the TB size (in bits) -// and the number of codeblocks. -static unsigned get_tb_and_crc_size(unsigned tb_size, unsigned nof_cbs) -{ - unsigned tb_and_crc_size = tb_size; - // If only one codeblock is transmitted, the CRC is taken into account by the decoder. If more than one codeblock is - // transmitted, there is an extra CRC of length 24 bits. - if (nof_cbs > 1) { - tb_and_crc_size += LONG_CRC_LENGTH; - } - - return tb_and_crc_size; -}; - // Returns, in order, the codeblock length, the message length and the number of data bits. // The message length is the number of systematic bits of the codeblock. This includes data and, if applicable, CRC, // zero padding and filler bits. @@ -94,40 +66,13 @@ static std::tuple get_cblk_bit_breakdown(const cod return {cb_length, msg_length, nof_data_bits}; } -static optional decode_cblk(bit_buffer& output, - span input, - ldpc_decoder* dec, - crc_calculator* crc, - const codeblock_metadata& cb_meta, - const pusch_decoder::configuration& cfg) -{ - ldpc_decoder::configuration::algorithm_details alg_details = {}; - alg_details.max_iterations = cfg.nof_ldpc_iterations; - // As for the other alg_details, we use the default values. - - if (cfg.use_early_stop) { - return dec->decode(output, input, crc, {cb_meta, alg_details}); - } - - // Without early stop, first decode and then check the CRC. - dec->decode(output, input, nullptr, {cb_meta, alg_details}); - - // Discard filler bits. - unsigned nof_significant_bits = output.size() - cb_meta.cb_specific.nof_filler_bits; - if (crc->calculate(output.first(nof_significant_bits)) == 0) { - return cfg.nof_ldpc_iterations; - } - - return nullopt; -} - pusch_decoder_buffer& pusch_decoder_impl::new_data(span transport_block_, - rx_softbuffer& softbuffer, + unique_rx_softbuffer softbuffer_, pusch_decoder_notifier& notifier, const pusch_decoder::configuration& cfg) { transport_block = transport_block_; - soft_codeword = &softbuffer; + softbuffer = std::move(softbuffer_); result_notifier = ¬ifier; current_config = cfg; softbits_count = 0; @@ -178,41 +123,33 @@ void pusch_decoder_impl::on_end_softbits() // Select view of LLRs. span llrs = span(softbits_buffer).first(softbits_count); - // Temporary buffer to store the rate-matched codeblocks (represented by LLRs) and their metadata. - static_vector codeblock_llrs = {}; // Recall that the TB is in packed format. unsigned tb_size = transport_block.size() * BITS_PER_BYTE; + codeblock_llrs.clear(); segmenter->segment(codeblock_llrs, llrs, tb_size, segmentation_config); unsigned nof_cbs = codeblock_llrs.size(); - srsran_assert(nof_cbs == soft_codeword->get_nof_codeblocks(), + srsran_assert(nof_cbs == softbuffer->get_nof_codeblocks(), "Wrong number of codeblocks {} (expected {}).", - soft_codeword->get_nof_codeblocks(), + softbuffer->get_nof_codeblocks(), nof_cbs); - unsigned tb_and_crc_size = get_tb_and_crc_size(tb_size, nof_cbs); - - // Temporary buffer to store the unpacked transport block (and, if applicable, its CRC). - static_bit_buffer tmp_tb_bits(tb_and_crc_size); - // Select CRC calculator for inner codeblock checks. crc_calculator* block_crc = select_crc(crc_set, tb_size, nof_cbs); // Reset CRCs if new data is flagged. - span cb_crcs = soft_codeword->get_codeblocks_crc(); + span cb_crcs = softbuffer->get_codeblocks_crc(); if (current_config.new_data) { srsvec::zero(cb_crcs); } - // Initialize decoder status. - pusch_decoder_result stats = {}; + // Set the atomic number of codeblocks. + cb_counter = nof_cbs; - unsigned tb_offset = 0; - stats.nof_codeblocks_total = nof_cbs; - stats.ldpc_decoder_stats.reset(); + // Iterate for each code block. for (unsigned cb_id = 0; cb_id != nof_cbs; ++cb_id) { - const span& cb_llrs = codeblock_llrs[cb_id].first; - const codeblock_metadata& cb_meta = codeblock_llrs[cb_id].second; + span cb_llrs = codeblock_llrs[cb_id].first; + const codeblock_metadata& cb_meta = codeblock_llrs[cb_id].second; srsran_assert(cb_llrs.size() == cb_meta.cb_specific.rm_length, "Wrong rate-matched codeblock length."); // Get codeblock length, without rate matching, the message length and the number of data bits (no CRC, no filler @@ -223,63 +160,159 @@ void pusch_decoder_impl::on_end_softbits() // Get data bits from previous transmissions, if any. // Messages are written on a dedicated buffer associated to the softbuffer. By doing this, we keep the decoded // message in memory and we don't need to compute it again if there is a retransmission. - bit_buffer message = soft_codeword->get_codeblock_data_bits(cb_id, msg_length); - - // Number of TB bits still "empty". - unsigned free_tb_bits = tb_and_crc_size - tb_offset; - // Avoid including zero-padding in the TB. - unsigned nof_new_bits = std::min(free_tb_bits, nof_data_bits); + bit_buffer message = softbuffer->get_codeblock_data_bits(cb_id, msg_length); // Get the LLRs from previous transmissions, if any, or a clean buffer. - span codeblock = soft_codeword->get_codeblock_soft_bits(cb_id, cb_length); - - // Dematch the new LLRs and combine them with the ones from previous transmissions. We do this everytime, including - // when the CRC for the codeblock is OK (from previous retransmissions), because we may need to decode it again if, - // eventually, we find out that the CRC of the entire transport block is KO. - dematcher->rate_dematch(codeblock, cb_llrs, current_config.new_data, cb_meta); + span rm_buffer = softbuffer->get_codeblock_soft_bits(cb_id, cb_length); + + // Code block processing task. + auto cb_process_task = [this, cb_meta, rm_buffer, cb_llrs, &cb_crc = cb_crcs[cb_id], block_crc, message]() { + // Check current CRC status. + if (cb_crc) { + // Dematch the new LLRs and combine them with the ones from previous transmissions. We do this everytime, + // including when the CRC for the codeblock is OK (from previous retransmissions), because we may need to + // decode it again if, eventually, we find out that the CRC of the entire transport block is KO. + decoder_pool->get().rate_match(rm_buffer, cb_llrs, current_config.new_data, cb_meta); + + if (cb_counter.fetch_sub(1) == 1) { + join_and_notify(); + } + return; + } - if (!cb_crcs[cb_id]) { // Try to decode. - optional nof_iters = decode_cblk(message, codeblock, decoder.get(), block_crc, cb_meta, current_config); + optional nof_iters = decoder_pool->get().decode(message, + rm_buffer, + cb_llrs, + current_config.new_data, + block_crc->get_generator_poly(), + current_config.use_early_stop, + current_config.nof_ldpc_iterations, + cb_meta); if (nof_iters.has_value()) { // If successful decoding, flag the CRC, record number of iterations and copy bits to the TB buffer. - cb_crcs[cb_id] = true; - stats.ldpc_decoder_stats.update(nof_iters.value()); + cb_crc = true; + cb_stats.push_blocking(nof_iters.value()); } else { - stats.ldpc_decoder_stats.update(current_config.nof_ldpc_iterations); + cb_stats.push_blocking(current_config.nof_ldpc_iterations); + } + + if (cb_counter.fetch_sub(1) == 1) { + join_and_notify(); } + }; + + // Execute task asynchronously if an executor is available and the number of codeblocks is larger than one. + bool enqueued = false; + if ((executor != nullptr) && (nof_cbs > 1)) { + enqueued = executor->execute(cb_process_task); } - // Copy the decoded code block into the transport block buffer. - srsvec::copy_offset(tmp_tb_bits, tb_offset, message, 0, nof_new_bits); + // Process task synchronously if is not successfully enqueued. + if (!enqueued) { + cb_process_task(); + } + } +} - tb_offset += nof_new_bits; +void pusch_decoder_impl::join_and_notify() +{ + unsigned nof_cbs = codeblock_llrs.size(); + span cb_crcs = softbuffer->get_codeblocks_crc(); + + // Initialize decoder status. + pusch_decoder_result stats = {}; + stats.nof_codeblocks_total = nof_cbs; + stats.ldpc_decoder_stats.reset(); + + // Calculate statistics. + optional cb_nof_iter = cb_stats.try_pop(); + while (cb_nof_iter.has_value()) { + stats.ldpc_decoder_stats.update(cb_nof_iter.value()); + cb_nof_iter = cb_stats.try_pop(); } - srsran_assert(tb_offset == tb_and_crc_size, "All TB bits should be filled at this point."); stats.tb_crc_ok = false; if (nof_cbs == 1) { // When only one codeblock, the CRC of codeblock and transport block are the same. stats.tb_crc_ok = cb_crcs[0]; + + // Copy the code block only nif the CRC is OK. if (stats.tb_crc_ok) { - srsvec::copy(transport_block, tmp_tb_bits.get_buffer().first(transport_block.size())); + const bit_buffer cb_data = softbuffer->get_codeblock_data_bits(0, transport_block.size() * BITS_PER_BYTE); + srsvec::copy(transport_block, cb_data.get_buffer()); } } else if (std::all_of(cb_crcs.begin(), cb_crcs.end(), [](bool a) { return a; })) { // When more than one codeblock, we need to check the global transport block CRC. Note that there is no need to // compute the CRC if any of the codeblocks was not decoded correctly. - srsvec::copy(transport_block, tmp_tb_bits.get_buffer().first(transport_block.size())); + unsigned tb_checksum = concatenate_codeblocks(); - if (crc_set.crc24A->calculate(tmp_tb_bits) == 0) { + bit_buffer tb_data = bit_buffer::from_bytes(transport_block); + if (crc_set.crc24A->calculate(tb_data) == tb_checksum) { stats.tb_crc_ok = true; } else { // If the checksum is wrong, then at least one of the codeblocks is a false negative. Reset all of them. - soft_codeword->reset_codeblocks_crc(); + softbuffer->reset_codeblocks_crc(); } } + // Release soft buffer if the CRC is OK, otherwise unlock. + if (stats.tb_crc_ok) { + softbuffer.release(); + } else { + softbuffer.unlock(); + } + // In case there are multiple codeblocks and at least one has a corrupted codeblock CRC, nothing to do. // Finally report decoding result. result_notifier->on_sch_data(stats); } + +unsigned pusch_decoder_impl::concatenate_codeblocks() +{ + unsigned nof_cbs = codeblock_llrs.size(); + bit_buffer tb_data = bit_buffer::from_bytes(transport_block); + + // Transport block write position. Bit index where the code block is copied. + unsigned tb_offset = 0; + + unsigned tb_checksum = 0; + + for (unsigned cb_id = 0; cb_id != nof_cbs; ++cb_id) { + const span& cb_llrs = codeblock_llrs[cb_id].first; + const codeblock_metadata& cb_meta = codeblock_llrs[cb_id].second; + srsran_assert(cb_llrs.size() == cb_meta.cb_specific.rm_length, "Wrong rate-matched codeblock length."); + + // Get codeblock length, without rate matching, the message length and the number of data bits (no CRC, no filler + // bits - may contain zero-padding). + unsigned cb_length = 0, msg_length = 0, nof_data_bits = 0; + std::tie(cb_length, msg_length, nof_data_bits) = get_cblk_bit_breakdown(cb_meta); + + // Number of TB bits still "empty". + unsigned free_tb_bits = tb_data.size() - tb_offset; + // Avoid including zero-padding in the TB. + unsigned nof_new_bits = std::min(free_tb_bits, nof_data_bits); + + // Get code block data from the buffer. + bit_buffer cb_data = softbuffer->get_codeblock_data_bits(cb_id, nof_data_bits); + + // Copy the decoded code block into the transport block buffer. + srsvec::copy_offset(tb_data, tb_offset, cb_data, 0, nof_new_bits); + + // Pack checksum if it is the last code block. + if (cb_id == nof_cbs - 1) { + for (unsigned i_byte = 0; i_byte != 3; ++i_byte) { + tb_checksum = (tb_checksum << 8U) | cb_data.extract(nof_new_bits + i_byte * 8, 8); + } + } + + // Increment transport block offset. + tb_offset += nof_new_bits; + } + + srsran_assert(tb_offset == tb_data.size(), "All TB bits should be filled at this point."); + + return tb_checksum; +} diff --git a/lib/phy/upper/channel_processors/pusch/pusch_decoder_impl.h b/lib/phy/upper/channel_processors/pusch/pusch_decoder_impl.h index 4a7d846ff5..2afe371432 100644 --- a/lib/phy/upper/channel_processors/pusch/pusch_decoder_impl.h +++ b/lib/phy/upper/channel_processors/pusch/pusch_decoder_impl.h @@ -22,20 +22,37 @@ #pragma once -#include "srsran/phy/upper/channel_coding/ldpc/ldpc_decoder.h" -#include "srsran/phy/upper/channel_coding/ldpc/ldpc_rate_dematcher.h" -#include "srsran/phy/upper/channel_coding/ldpc/ldpc_segmenter_rx.h" +#include "pusch_codeblock_decoder.h" +#include "srsran/adt/concurrent_queue.h" #include "srsran/phy/upper/channel_processors/pusch/pusch_decoder.h" #include "srsran/phy/upper/channel_processors/pusch/pusch_decoder_buffer.h" -#include "srsran/phy/upper/codeblock_metadata.h" +#include "srsran/phy/upper/unique_rx_softbuffer.h" #include "srsran/ran/pdsch/pdsch_constants.h" +#include "srsran/support/executors/task_executor.h" +#include "srsran/support/memory_pool/concurrent_thread_local_object_pool.h" namespace srsran { +// Number of bits in one byte. +static constexpr unsigned BITS_PER_BYTE = 8; + +// Maximum TBS that implies a 16-bit CRC. +constexpr unsigned MAX_BITS_CRC16 = 3824; + +// Number of bits in the long CRC. A CRC of this length is used either for TB CRCs, when the TB is longer than +// MAX_BITS_CRC16, or as a codeblock CRC, when the TB consists of multiple codeblocks. +constexpr unsigned LONG_CRC_LENGTH = 24; + +// Maximum accepted transport block size. +static constexpr unsigned MAX_TBS = 1277992; + /// Implementation of the PUSCH decoder. class pusch_decoder_impl : public pusch_decoder, private pusch_decoder_buffer { public: + /// Code block decoder pool type. + using codeblock_decoder_pool = concurrent_thread_local_object_pool; + /// CRC calculators used in shared channels. struct sch_crc { /// For short TB checksums. @@ -51,49 +68,56 @@ class pusch_decoder_impl : public pusch_decoder, private pusch_decoder_buffer /// Sets up the internal components, namely LDPC segmenter, LDPC rate dematcher, LDPC decoder and all the CRC /// calculators. /// - /// \param[in] seg Pointer to an LDPC segmenter object. - /// \param[in] rdem Pointer to an LDPC rate dematcher object. - /// \param[in] dec Pointer to an LDPC decoder object. - /// \param[in] crcs Structure with pointers to three CRC calculator objects, with generator polynomials of type \c - /// CRC16, \c CRC24A and \c CRC24B. - pusch_decoder_impl(std::unique_ptr seg, - std::unique_ptr rdem, - std::unique_ptr dec, - sch_crc crcs) : - segmenter(std::move(seg)), - dematcher(std::move(rdem)), - decoder(std::move(dec)), - crc_set({std::move(crcs.crc16), std::move(crcs.crc24A), std::move(crcs.crc24B)}), - softbits_buffer(pdsch_constants::CODEWORD_MAX_SIZE.value()) + /// \param[in] segmenter_ LDPC segmenter. + /// \param[in] decoder_pool_ Codeblock decoder. + /// \param[in] crc_set_ Structure with pointers to three CRC calculator objects, with generator + /// polynomials of type \c CRC16, \c CRC24A and \c CRC24B. + /// \param[in] executor_ Task executor for asynchronous PUSCH code block decoding. + pusch_decoder_impl(std::unique_ptr segmenter_, + std::shared_ptr decoder_pool_, + sch_crc crc_set_, + task_executor* executor_) : + segmenter(std::move(segmenter_)), + decoder_pool(std::move(decoder_pool_)), + crc_set(std::move(crc_set_)), + executor(executor_), + softbits_buffer(pdsch_constants::CODEWORD_MAX_SIZE.value()), + cb_stats(MAX_NOF_SEGMENTS) { srsran_assert(segmenter, "Invalid segmenter."); - srsran_assert(dematcher, "Invalid dematcher."); + srsran_assert(decoder_pool, "Invalid codeblock decoder pool."); srsran_assert(crc_set.crc16, "Invalid CRC16 calculator."); srsran_assert(crc_set.crc24A, "Invalid CRC24A calculator."); srsran_assert(crc_set.crc24B, "Invalid CRC24B calculator."); srsran_assert(crc_set.crc16->get_generator_poly() == crc_generator_poly::CRC16, "Wrong TB CRC calculator."); srsran_assert(crc_set.crc24A->get_generator_poly() == crc_generator_poly::CRC24A, "Wrong TB CRC calculator."); srsran_assert(crc_set.crc24B->get_generator_poly() == crc_generator_poly::CRC24B, "Wrong TB CRC calculator."); + if (executor != nullptr) { + srsran_assert(decoder_pool->capacity() > 1, + "The number of PUSCH code block decoder in the pool (i.e., {}) must be greater than one.", + decoder_pool->capacity()); + } }; // See interface for the documentation. pusch_decoder_buffer& new_data(span transport_block, - rx_softbuffer& softbuffer, + unique_rx_softbuffer softbuffer, pusch_decoder_notifier& notifier, const configuration& cfg) override; private: /// Pointer to an LDPC segmenter. std::unique_ptr segmenter; - /// Pointer to an LDPC rate-dematcher. - std::unique_ptr dematcher; - /// Pointer to an LDPC decoder. - std::unique_ptr decoder; + /// Pointer to a codeblock decoder. + std::shared_ptr decoder_pool; /// \brief Pointer to a CRC calculator for TB-wise checksum. /// /// Only the CRC calculator with generator polynomial crc_generator_poly::CRC24A, used for long transport blocks, is /// needed. Indeed, if a transport block is short enough not to be segmented, the CRC is verified by the decoder. sch_crc crc_set; + /// Optional task executor. Used for accelerating the PUSCH decoding at code block level. Set to \c nullptr for no + /// concurrent execution. + task_executor* executor; /// Soft bit buffer. std::vector softbits_buffer; /// Counts the number of soft bits in the buffer. @@ -101,11 +125,17 @@ class pusch_decoder_impl : public pusch_decoder, private pusch_decoder_buffer /// Current transport block. span transport_block; /// Current soft bits buffer. - rx_softbuffer* soft_codeword; + unique_rx_softbuffer softbuffer; /// Current notifier. - pusch_decoder_notifier* result_notifier; + pusch_decoder_notifier* result_notifier = nullptr; /// Current PUSCH decoder configuration. pusch_decoder::configuration current_config; + /// Temporary buffer to store the rate-matched codeblocks (represented by LLRs) and their metadata. + static_vector codeblock_llrs = {}; + /// Counts code blocks. + std::atomic cb_counter; + /// Enqueues code block decoder statistics. + concurrent_queue cb_stats; // See interface for the documentation. span get_next_block_view(unsigned block_size) override; @@ -115,6 +145,15 @@ class pusch_decoder_impl : public pusch_decoder, private pusch_decoder_buffer // See interface for the documentation. void on_end_softbits() override; + + /// \brief Joins the multiple code block processing. + /// + /// Called from the last decoding code block task. It concatenates code blocks and checks the decoded transport block + /// CRC if applicable. Also, it notifies the decoder result. + void join_and_notify(); + + /// Concatenates code blocks and returns the CRC checksum. + unsigned concatenate_codeblocks(); }; } // namespace srsran diff --git a/lib/phy/upper/channel_processors/pusch/pusch_processor_impl.cpp b/lib/phy/upper/channel_processors/pusch/pusch_processor_impl.cpp index 5da50367a7..17f9cb65dc 100644 --- a/lib/phy/upper/channel_processors/pusch/pusch_processor_impl.cpp +++ b/lib/phy/upper/channel_processors/pusch/pusch_processor_impl.cpp @@ -205,7 +205,7 @@ pusch_processor_impl::pusch_processor_impl(pusch_processor_configuration& config } void pusch_processor_impl::process(span data, - rx_softbuffer& softbuffer, + unique_rx_softbuffer softbuffer, pusch_processor_result_notifier& notifier, const resource_grid_reader& grid, const pusch_processor::pdu_t& pdu) @@ -311,7 +311,7 @@ void pusch_processor_impl::process(span data, csi_part2_decoder, *demultiplex, pdu.mcs_descr.modulation, pdu.uci.csi_part2_size, ulsch_config); // Prepare notifiers. - pusch_processor_notifier_adaptor notifier_adaptor(notifier, csi, csi_part1_feedback); + notifier_adaptor.new_transmission(notifier, csi_part1_feedback, csi); csi_part1_feedback.connect_notifier(notifier_adaptor); if (has_sch_data) { @@ -327,7 +327,8 @@ void pusch_processor_impl::process(span data, decoder_config.new_data = pdu.codeword.value().new_data; // Setup decoder. - decoder_buffer = decoder->new_data(data, softbuffer, notifier_adaptor.get_sch_data_notifier(), decoder_config); + decoder_buffer = + decoder->new_data(data, std::move(softbuffer), notifier_adaptor.get_sch_data_notifier(), decoder_config); } // Prepares HARQ-ACK notifier and buffer. diff --git a/lib/phy/upper/channel_processors/pusch/pusch_processor_impl.h b/lib/phy/upper/channel_processors/pusch/pusch_processor_impl.h index 3b3fc80f1c..2ccfa85d1c 100644 --- a/lib/phy/upper/channel_processors/pusch/pusch_processor_impl.h +++ b/lib/phy/upper/channel_processors/pusch/pusch_processor_impl.h @@ -31,6 +31,7 @@ #include "srsran/phy/upper/channel_processors/pusch/ulsch_demultiplex.h" #include "srsran/phy/upper/channel_processors/uci_decoder.h" #include "srsran/phy/upper/signal_processors/dmrs_pusch_estimator.h" +#include "srsran/phy/upper/unique_rx_softbuffer.h" #include "srsran/ran/pusch/ulsch_info.h" #include @@ -96,7 +97,7 @@ class pusch_processor_impl : public pusch_processor // See interface for documentation. void process(span data, - rx_softbuffer& softbuffer, + unique_rx_softbuffer softbuffer, pusch_processor_result_notifier& notifier, const resource_grid_reader& grid, const pdu_t& pdu) override; @@ -156,6 +157,8 @@ class pusch_processor_impl : public pusch_processor bool dec_enable_early_stop; /// Selects the PUSCH SINR calculation method. channel_state_information::sinr_type csi_sinr_calc_method; + /// Notifier adaptor. + pusch_processor_notifier_adaptor notifier_adaptor; }; } // namespace srsran diff --git a/lib/phy/upper/channel_processors/pusch/pusch_processor_notifier_adaptor.h b/lib/phy/upper/channel_processors/pusch/pusch_processor_notifier_adaptor.h index 942448a159..e018656c7a 100644 --- a/lib/phy/upper/channel_processors/pusch/pusch_processor_notifier_adaptor.h +++ b/lib/phy/upper/channel_processors/pusch/pusch_processor_notifier_adaptor.h @@ -33,14 +33,16 @@ namespace srsran { namespace detail { -/// \brief PUSCH processor notifier adaptor internal UCI callback. +/// \brief PUSCH processor notifier adaptor internal callback. /// -/// Interfaces the different UCI notifiers with the notifier adaptor. -class pusch_processor_notifier_uci_callback +/// Interfaces the different notifiers with the notifier adaptor. +class pusch_processor_notifier_callback { public: /// Default destructor. - virtual ~pusch_processor_notifier_uci_callback() = default; + virtual ~pusch_processor_notifier_callback() = default; + + virtual void on_sch_data(const pusch_decoder_result& result) = 0; /// Notifies HARQ-ACK. virtual void on_harq_ack(const pusch_uci_field& field) = 0; @@ -65,25 +67,34 @@ class pusch_processor_csi_part1_feedback /// \brief Adapts the notifiers of each PUSCH decoder to the PUSCH processor notifier. /// /// The UCI fields notifier getters set flags of their respective fields as pending. -class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifier_uci_callback +class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifier_callback { public: /// Creates a notifier adaptor from a PUSCH processor notifier. - pusch_processor_notifier_adaptor(pusch_processor_result_notifier& notifier_, - const channel_state_information& csi, - pusch_processor_csi_part1_feedback& csi_part_1_feedback_) : - notifier(notifier_), + pusch_processor_notifier_adaptor() : pusch_demod_notifier(uci_payload.csi), - sch_data_notifier(notifier, uci_payload.csi), + sch_data_notifier(*this), harq_ack_notifier(*this), csi_part_1_notifier(*this), - csi_part_2_notifier(*this), - csi_part_1_feedback(csi_part_1_feedback_) + csi_part_2_notifier(*this) { uci_payload.harq_ack.clear(); uci_payload.csi_part1.clear(); uci_payload.csi_part2.clear(); - uci_payload.csi = csi; + } + + /// \brief Configures the notifier for a new transmission. + /// \param[in] notifier_ PUSCH processor result notifier. + /// \param[in] csi_part_1_feedback_ Uplink control information field CSI Part 1 feedback notifier. + /// \param[in] csi Channel state information. + /// \return A PUSCH processor notifier adaptor. + void new_transmission(pusch_processor_result_notifier& notifier_, + pusch_processor_csi_part1_feedback& csi_part_1_feedback_, + const channel_state_information& csi) + { + notifier = ¬ifier_; + csi_part_1_feedback = &csi_part_1_feedback_; + uci_payload.csi = csi; } /// Gets the PUSCH demodulator notifier. @@ -116,7 +127,8 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie return csi_part_2_notifier; } - ~pusch_processor_notifier_adaptor() + /// Default destructor - verifies that the previous fields are valid. + ~pusch_processor_notifier_adaptor() override { srsran_assert(!pending_harq_ack, "HARQ-ACK has not been notified."); srsran_assert(!pending_csi_part1, "CSI Part 1 has not been notified."); @@ -155,30 +167,20 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie class sch_data_notifier_impl : public pusch_decoder_notifier { public: - sch_data_notifier_impl(pusch_processor_result_notifier& notifier_, channel_state_information& csi_) : - notifier(notifier_), csi(csi_) - { - } + sch_data_notifier_impl(detail::pusch_processor_notifier_callback& notifier_) : notifier(notifier_) {} // See interface for documentation. - void on_sch_data(const pusch_decoder_result& result) override - { - pusch_processor_result_data result_data; - result_data.data = result; - result_data.csi = csi; - notifier.on_sch(result_data); - } + void on_sch_data(const pusch_decoder_result& result) override { notifier.on_sch_data(result); } private: - pusch_processor_result_notifier& notifier; - channel_state_information& csi; + detail::pusch_processor_notifier_callback& notifier; }; /// Implements the HARQ-ACK notifier. class harq_ack_notifier_impl : public pusch_uci_decoder_notifier { public: - harq_ack_notifier_impl(detail::pusch_processor_notifier_uci_callback& callback_) : callback(callback_) {} + harq_ack_notifier_impl(detail::pusch_processor_notifier_callback& callback_) : callback(callback_) {} // See interface for documentation. void on_uci_decoded(span message, const uci_status& status) override @@ -191,14 +193,14 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie } private: - detail::pusch_processor_notifier_uci_callback& callback; + detail::pusch_processor_notifier_callback& callback; }; /// Implements the CSI Part 1 notifier. class csi_part1_notifier_impl : public pusch_uci_decoder_notifier { public: - csi_part1_notifier_impl(detail::pusch_processor_notifier_uci_callback& callback_) : callback(callback_) {} + csi_part1_notifier_impl(detail::pusch_processor_notifier_callback& callback_) : callback(callback_) {} // See interface for documentation. void on_uci_decoded(span message, const uci_status& status) override @@ -211,14 +213,14 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie } private: - detail::pusch_processor_notifier_uci_callback& callback; + detail::pusch_processor_notifier_callback& callback; }; /// Implements the CSI Part 2 notifier. class csi_part2_notifier_impl : public pusch_uci_decoder_notifier { public: - csi_part2_notifier_impl(detail::pusch_processor_notifier_uci_callback& callback_) : callback(callback_) {} + csi_part2_notifier_impl(detail::pusch_processor_notifier_callback& callback_) : callback(callback_) {} // See interface for documentation. void on_uci_decoded(span message, const uci_status& status) override @@ -231,10 +233,18 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie } private: - detail::pusch_processor_notifier_uci_callback& callback; + detail::pusch_processor_notifier_callback& callback; }; - // See detail::pusch_processor_notifier_uci_callback for documentation. + void on_sch_data(const pusch_decoder_result& result) override + { + pusch_processor_result_data result_data; + result_data.data = result; + result_data.csi = uci_payload.csi; + notifier->on_sch(result_data); + } + + // See detail::pusch_processor_notifier_callback for documentation. void on_harq_ack(const pusch_uci_field& field) override { // Check HARQ ACK is pending. @@ -250,14 +260,15 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie check_and_notify_uci(); } - // See detail::pusch_processor_notifier_uci_callback for documentation. + // See detail::pusch_processor_notifier_callback for documentation. void on_csi_part1(const pusch_uci_field& field) override { // Check CSI Part 1 is pending. srsran_assert(pending_csi_part1, "CSI Part 1 is not pending."); if (field.status == uci_status::valid) { - csi_part_1_feedback.on_csi_part1(field.payload); + srsran_assert(csi_part_1_feedback != nullptr, "Invalid CSI Part 1 feedback."); + csi_part_1_feedback->on_csi_part1(field.payload); } // Set CSI Part 1 field. @@ -270,7 +281,7 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie check_and_notify_uci(); } - // See detail::pusch_processor_notifier_uci_callback for documentation. + // See detail::pusch_processor_notifier_callback for documentation. void on_csi_part2(const pusch_uci_field& field) override { // Check CSI Part 2 is pending. @@ -289,8 +300,9 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie /// Check if the no UCI fields are pending and notify the results. void check_and_notify_uci() { + srsran_assert(notifier != nullptr, "Invalid notifier."); if (!pending_harq_ack && !pending_csi_part1 && !pending_csi_part2) { - notifier.on_uci(uci_payload); + notifier->on_uci(uci_payload); } } @@ -303,7 +315,9 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie /// Stored UCI payload. pusch_processor_result_control uci_payload = {}; /// Reference to PUSCH processor notifier. - pusch_processor_result_notifier& notifier; + pusch_processor_result_notifier* notifier; + /// CSI Part 1 feedback. + pusch_processor_csi_part1_feedback* csi_part_1_feedback; /// Channel state information notifier. pusch_demodulator_notifier_impl pusch_demod_notifier; /// SCH data notifier. @@ -314,8 +328,6 @@ class pusch_processor_notifier_adaptor : private detail::pusch_processor_notifie csi_part1_notifier_impl csi_part_1_notifier; /// CSI Part 2 notifier. csi_part2_notifier_impl csi_part_2_notifier; - /// CSI Part 1 feedback. - pusch_processor_csi_part1_feedback& csi_part_1_feedback; }; } // namespace srsran diff --git a/lib/phy/upper/channel_processors/pusch/pusch_processor_pool.h b/lib/phy/upper/channel_processors/pusch/pusch_processor_pool.h new file mode 100644 index 0000000000..90cb112c75 --- /dev/null +++ b/lib/phy/upper/channel_processors/pusch/pusch_processor_pool.h @@ -0,0 +1,147 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/adt/concurrent_queue.h" +#include "srsran/adt/ring_buffer.h" +#include "srsran/phy/upper/channel_processors/channel_processor_formatters.h" +#include "srsran/phy/upper/channel_processors/pusch/pusch_processor.h" +#include "srsran/srslog/logger.h" + +namespace srsran { + +namespace detail { + +/// Free PUSCH processor identifier list type. +using pusch_processor_free_list = + concurrent_queue; + +/// PUSCH processor wrapper. It appends its identifier into the free list when the processing is finished. +class pusch_processor_wrapper : public pusch_processor, private pusch_processor_result_notifier +{ +public: + /// Creates a pusch processor wrapper from another pusch processor. + explicit pusch_processor_wrapper(unsigned index_, + pusch_processor_free_list& free_list_, + std::unique_ptr processor_) : + index(index_), free_list(free_list_), notifier(nullptr), processor(std::move(processor_)) + { + srsran_assert(processor, "Invalid pusch processor."); + } + + /// Creates a PUSCH processor wrapper from another PUSCH processor wrapper. + pusch_processor_wrapper(pusch_processor_wrapper&& other) : + index(other.index), free_list(other.free_list), notifier(other.notifier), processor(std::move(other.processor)) + { + other.notifier = nullptr; + } + + // See pusch_processor interface for documentation. + void process(span data, + unique_rx_softbuffer softbuffer, + pusch_processor_result_notifier& notifier_, + const resource_grid_reader& grid, + const pdu_t& pdu) override + { + // Save original notifier. + notifier = ¬ifier_; + + // Process. + processor->process(data, std::move(softbuffer), *this, grid, pdu); + } + +private: + // See pusch_processor_result_notifier for documentation. + void on_uci(const pusch_processor_result_control& uci) override + { + srsran_assert(notifier != nullptr, "Invalid notifier."); + notifier->on_uci(uci); + } + + // See pusch_processor_result_notifier for documentation. + void on_sch(const pusch_processor_result_data& sch) override + { + srsran_assert(notifier != nullptr, "Invalid notifier."); + + // Notify the completion of the processing. + notifier->on_sch(sch); + + // Return the pusch processor identifier to the free list. + free_list.push_blocking(index); + } + + /// Processor identifier within the pool. + unsigned index; + /// List of free processors. + pusch_processor_free_list& free_list; + /// Current pusch processor notifier. + pusch_processor_result_notifier* notifier = nullptr; + /// Wrapped pusch processor. + std::unique_ptr processor; +}; +} // namespace detail + +/// \brief PUSCH processor pool +/// +/// It contains PUSCH processors that are asynchronously executed. The processing of a PUSCH transmission is dropped if +/// there are no free PUSCH processors available. +/// +class pusch_processor_pool : public pusch_processor +{ +public: + /// Creates a PUSCH processor pool from a list of processors. Ownership is transferred to the pool. + explicit pusch_processor_pool(span> processors_) : free_list(processors_.size()) + { + unsigned index = 0; + for (std::unique_ptr& processor : processors_) { + free_list.push_blocking(index); + processors.emplace_back(detail::pusch_processor_wrapper(index++, free_list, std::move(processor))); + } + } + + // See interface for documentation. + void process(span data, + unique_rx_softbuffer softbuffer, + pusch_processor_result_notifier& notifier, + const resource_grid_reader& grid, + const pdu_t& pdu) override + { + // Try to get a worker. + optional index = free_list.try_pop(); + + // If no worker is available. + if (!index.has_value()) { + srslog::fetch_basic_logger("PHY").warning("Insufficient number of PUSCH processors. Dropping PUSCH {:s}.", pdu); + return; + } + + // Process pusch. + processors[index.value()].process(data, std::move(softbuffer), notifier, grid, pdu); + } + +private: + std::vector processors; + detail::pusch_processor_free_list free_list; +}; + +} // namespace srsran \ No newline at end of file diff --git a/lib/phy/upper/downlink_processor_single_executor_impl.cpp b/lib/phy/upper/downlink_processor_single_executor_impl.cpp index 5b2615e94f..1650215790 100644 --- a/lib/phy/upper/downlink_processor_single_executor_impl.cpp +++ b/lib/phy/upper/downlink_processor_single_executor_impl.cpp @@ -22,6 +22,7 @@ #include "downlink_processor_single_executor_impl.h" #include "srsran/phy/support/resource_grid_mapper.h" +#include "srsran/phy/upper/channel_processors/channel_processor_formatters.h" #include "srsran/phy/upper/upper_phy_rg_gateway.h" #include "srsran/support/executors/task_executor.h" @@ -40,7 +41,8 @@ downlink_processor_single_executor_impl::downlink_processor_single_executor_impl pdsch_proc(std::move(pdsch_proc_)), ssb_proc(std::move(ssb_proc_)), csi_rs_proc(std::move(csi_rs_proc_)), - executor(executor_) + executor(executor_), + pdsch_notifier(*this) { srsran_assert(pdcch_proc, "Invalid PDCCH processor received."); srsran_assert(pdsch_proc, "Invalid PDSCH processor received."); @@ -104,11 +106,14 @@ bool downlink_processor_single_executor_impl::process_pdsch( // Do not execute if the grid is not available. if (current_grid != nullptr) { resource_grid_mapper& mapper = current_grid->get_mapper(); - pdsch_proc->process(mapper, data, pdu); - } + pdsch_proc->process(mapper, pdsch_notifier, data, pdu); + } else { + // Inform about the dropped PDSCH. + srslog::fetch_basic_logger("PHY").warning("Failed to execute. Dropping PDSCH {:s}.", pdu); - // Report task completion to FSM. - on_task_completion(); + // Report task drop to FSM. + on_task_completion(); + } }); // If que task could not be enqueued. diff --git a/lib/phy/upper/downlink_processor_single_executor_impl.h b/lib/phy/upper/downlink_processor_single_executor_impl.h index 120a89207e..201b7b6c40 100644 --- a/lib/phy/upper/downlink_processor_single_executor_impl.h +++ b/lib/phy/upper/downlink_processor_single_executor_impl.h @@ -32,6 +32,22 @@ namespace srsran { class upper_phy_rg_gateway; class task_executor; +namespace detail { + +class downlink_processor_callback +{ +public: + virtual ~downlink_processor_callback() = default; + + /// Sends the resource grid and updates the processor state to allow configuring a new resource grid. + virtual void send_resource_grid() = 0; + + /// Decrements the number of pending PDUs to be processed and tries to send the resource grid through the gateway. + virtual void on_task_completion() = 0; +}; + +} // namespace detail + /// \brief Implementation of a downlink processor. /// /// This implementation process the PDUs and config using one executor, which @@ -46,7 +62,7 @@ class task_executor; /// the gateway as soon as every enqueued PDU before finish_processing_pdus() is processed . This is controlled counting /// the PDUs that are processed and finished processing. /// \note Thread safe class. -class downlink_processor_single_executor_impl : public downlink_processor +class downlink_processor_single_executor_impl : public downlink_processor, private detail::downlink_processor_callback { public: /// \brief Builds a downlink processor single executor impl object with the given parameters. @@ -84,12 +100,26 @@ class downlink_processor_single_executor_impl : public downlink_processor void finish_processing_pdus() override; private: + class pdsch_processor_notifier_wrapper : public pdsch_processor_notifier + { + public: + pdsch_processor_notifier_wrapper(detail::downlink_processor_callback& callback_) : callback(callback_) + { + // Do nothing. + } + + void on_finish_processing() override { callback.on_task_completion(); } + + private: + detail::downlink_processor_callback& callback; + }; + /// \brief Sends the resource grid and updates the processor state to allow configuring a new resource grid. - void send_resource_grid(); + void send_resource_grid() override; /// \brief Decrements the number of pending PDUs to be processed and tries to send the resource grid through the /// gateway. - void on_task_completion(); + void on_task_completion() override; upper_phy_rg_gateway& gateway; resource_grid_context rg_context; @@ -103,6 +133,9 @@ class downlink_processor_single_executor_impl : public downlink_processor /// DL processor internal state. downlink_processor_single_executor_state state; + /// PDSCH notifier wrapper. + pdsch_processor_notifier_wrapper pdsch_notifier; + /// Protects the internal state. // :TODO: remove me later mutable std::mutex mutex; diff --git a/lib/phy/upper/rx_softbuffer_impl.h b/lib/phy/upper/rx_softbuffer_impl.h index d698678dcd..9853e15c06 100644 --- a/lib/phy/upper/rx_softbuffer_impl.h +++ b/lib/phy/upper/rx_softbuffer_impl.h @@ -31,6 +31,21 @@ namespace srsran { +enum class rx_softbuffer_status { successful = 0, already_in_use, insuficient_cb }; + +constexpr const char* to_string(rx_softbuffer_status status) +{ + switch (status) { + default: + case rx_softbuffer_status::successful: + return "successful"; + case rx_softbuffer_status::already_in_use: + return "HARQ already in use"; + case rx_softbuffer_status::insuficient_cb: + return "insufficient CBs"; + } +} + /// Implements a receiver softbuffer interface. class rx_softbuffer_impl : public unique_rx_softbuffer::softbuffer { @@ -109,13 +124,14 @@ class rx_softbuffer_impl : public unique_rx_softbuffer::softbuffer /// \param[in] expire_slot Slot at which the reservation expires. /// \param[in] nof_codeblocks Number of codeblocks to reserve. /// \return True if the reservation is successful, false otherwise. - bool reserve(const rx_softbuffer_identifier& id, const slot_point& expire_slot, unsigned int nof_codeblocks) + rx_softbuffer_status + reserve(const rx_softbuffer_identifier& id, const slot_point& expire_slot, unsigned int nof_codeblocks) { std::unique_lock lock(fsm_mutex); // It cannot be reserved if it is locked. if (current_state == state::locked) { - return false; + return rx_softbuffer_status::already_in_use; } // Update reservation information. @@ -126,7 +142,7 @@ class rx_softbuffer_impl : public unique_rx_softbuffer::softbuffer if (nof_codeblocks == codeblock_ids.size()) { // Transitions to reserved if it is available or released. current_state = state::reserved; - return true; + return rx_softbuffer_status::successful; } // Make sure there are no buffers before reserving. @@ -147,13 +163,13 @@ class rx_softbuffer_impl : public unique_rx_softbuffer::softbuffer if (cb_id == rx_softbuffer_codeblock_pool::UNRESERVED_CB_ID) { // Free the rest of the softbuffer. free(); - return false; + return rx_softbuffer_status::insuficient_cb; } } // Transition to reserved. current_state = state::reserved; - return true; + return rx_softbuffer_status::successful; } /// \brief Runs softbuffer housekeeping as per slot basis. diff --git a/lib/phy/upper/rx_softbuffer_pool_impl.cpp b/lib/phy/upper/rx_softbuffer_pool_impl.cpp index 136b320079..d22a22bfeb 100644 --- a/lib/phy/upper/rx_softbuffer_pool_impl.cpp +++ b/lib/phy/upper/rx_softbuffer_pool_impl.cpp @@ -24,6 +24,27 @@ using namespace srsran; +namespace fmt { + +/// Default formatter for rx_softbuffer_identifier. +template <> +struct formatter { + template + auto parse(ParseContext& ctx) -> decltype(ctx.begin()) + { + return ctx.begin(); + } + + template + auto format(const srsran::rx_softbuffer_identifier& value, FormatContext& ctx) + -> decltype(std::declval().out()) + { + return format_to(ctx.out(), "rnti={:#x} h_id={}", value.rnti, value.harq_ack_id); + } +}; + +} // namespace fmt + unique_rx_softbuffer rx_softbuffer_pool_impl::reserve_softbuffer(const slot_point& slot, const rx_softbuffer_identifier& id, unsigned nof_codeblocks) @@ -34,8 +55,13 @@ unique_rx_softbuffer rx_softbuffer_pool_impl::reserve_softbuffer(const slot_poin // Look for the same identifier within the reserved buffers. for (auto& buffer : reserved_buffers) { if (buffer->match_id(id)) { + rx_softbuffer_status status = buffer->reserve(id, expire_slot, nof_codeblocks); + // Reserve buffer. - if (!buffer->reserve(id, expire_slot, nof_codeblocks)) { + if (status != rx_softbuffer_status::successful) { + logger.set_context(slot.sfn(), slot.slot_index()); + logger.warning("UL HARQ {}: failed to reserve, {}.", id, to_string(status)); + // If the reservation failed, return an invalid buffer. return unique_rx_softbuffer(); } @@ -46,6 +72,8 @@ unique_rx_softbuffer rx_softbuffer_pool_impl::reserve_softbuffer(const slot_poin // If no available buffer is found, return an invalid buffer. if (available_buffers.empty()) { + logger.set_context(slot.sfn(), slot.slot_index()); + logger.warning("UL HARQ {}: failed to reserve, insufficient buffers in the pool.", id); return unique_rx_softbuffer(); } @@ -53,10 +81,10 @@ unique_rx_softbuffer rx_softbuffer_pool_impl::reserve_softbuffer(const slot_poin std::unique_ptr& buffer = available_buffers.top(); // Try to reserve codeblocks. - bool reserved = buffer->reserve(id, expire_slot, nof_codeblocks); + rx_softbuffer_status status = buffer->reserve(id, expire_slot, nof_codeblocks); // Move the buffer to reserved list and remove from available if the reservation was successful. - if (reserved) { + if (status == rx_softbuffer_status::successful) { unique_rx_softbuffer unique_buffer(*buffer); reserved_buffers.push(std::move(buffer)); available_buffers.pop(); @@ -64,6 +92,8 @@ unique_rx_softbuffer rx_softbuffer_pool_impl::reserve_softbuffer(const slot_poin } // If the reservation failed, return an invalid buffer. + logger.set_context(slot.sfn(), slot.slot_index()); + logger.warning("UL HARQ {}: failed to reserve, {}.", id, to_string(status)); return unique_rx_softbuffer(); } diff --git a/lib/phy/upper/rx_softbuffer_pool_impl.h b/lib/phy/upper/rx_softbuffer_pool_impl.h index 0a51765950..4a6c588b11 100644 --- a/lib/phy/upper/rx_softbuffer_pool_impl.h +++ b/lib/phy/upper/rx_softbuffer_pool_impl.h @@ -47,6 +47,8 @@ class rx_softbuffer_pool_impl : public rx_softbuffer_pool unsigned expire_timeout_slots; /// Protects methods from concurrent calls. std::mutex mutex; + /// Logger. + srslog::basic_logger& logger; public: /// \brief Creates a generic receiver softbuffer pool. @@ -55,7 +57,8 @@ class rx_softbuffer_pool_impl : public rx_softbuffer_pool codeblock_pool(config.max_nof_codeblocks, config.max_codeblock_size, config.external_soft_bits), available_buffers(config.max_softbuffers), reserved_buffers(config.max_softbuffers), - expire_timeout_slots(config.expire_timeout_slots) + expire_timeout_slots(config.expire_timeout_slots), + logger(srslog::fetch_basic_logger("PHY", true)) { for (unsigned i = 0, i_end = config.max_softbuffers; i != i_end; ++i) { available_buffers.push(std::make_unique(codeblock_pool)); diff --git a/lib/phy/upper/signal_processors/port_channel_estimator_average_impl.cpp b/lib/phy/upper/signal_processors/port_channel_estimator_average_impl.cpp index 41b342ce1a..e8e443837d 100644 --- a/lib/phy/upper/signal_processors/port_channel_estimator_average_impl.cpp +++ b/lib/phy/upper/signal_processors/port_channel_estimator_average_impl.cpp @@ -190,6 +190,12 @@ void port_channel_estimator_average_impl::compute(channel_estimate& es estimate.set_time_alignment(phy_time_unit::from_seconds(time_alignment_s), port, i_layer); noise_var /= static_cast(nof_dmrs_pilots - 1); + + // Bound the noise variance from below. + float min_noise_variance = rsrp / convert_dB_to_power(MAX_SINR_DB); + noise_var = std::max(min_noise_variance, noise_var); + + // Write the noise variance in the channel estimate result. estimate.set_noise_variance(noise_var, port, i_layer); srsran_assert(cfg.scaling > 0, "The DM-RS to data scaling factor should be a positive number."); diff --git a/lib/phy/upper/signal_processors/port_channel_estimator_average_impl.h b/lib/phy/upper/signal_processors/port_channel_estimator_average_impl.h index 7c4b34afba..e27f81d4c6 100644 --- a/lib/phy/upper/signal_processors/port_channel_estimator_average_impl.h +++ b/lib/phy/upper/signal_processors/port_channel_estimator_average_impl.h @@ -41,6 +41,10 @@ class port_channel_estimator_average_impl : public port_channel_estimator /// The inverse DFT is used to estimate the time alignment. A DFT size of 4096 points allows of a resolution of 16.3 /// and 8.1 nanoseconds with a subcarrier spacing of 15 kHz and 30 kHz, respectively. static constexpr unsigned DFT_SIZE = 4096; + /// \brief Maximum SINR in decibels. + /// + /// The SINR is bounded above to avoid a zero noise variance. + static constexpr float MAX_SINR_DB = 100; /// Constructor - Sets the internal interpolator and inverse DFT processor of size \c DFT_SIZE. port_channel_estimator_average_impl(std::unique_ptr interp, std::unique_ptr idft_proc) : diff --git a/lib/phy/upper/uplink_processor_impl.cpp b/lib/phy/upper/uplink_processor_impl.cpp index 855ff81e0d..060c72a260 100644 --- a/lib/phy/upper/uplink_processor_impl.cpp +++ b/lib/phy/upper/uplink_processor_impl.cpp @@ -28,87 +28,6 @@ using namespace srsran; -namespace { - -/// \brief Adapts the PUSCH processor result notifier to the upper PHY receive results notifier. -/// -/// It collects the Channel State Information (CSI), control and data decoding information from a PUSCH processor object -/// and notifies them through the upper PHY notification interface. -/// -/// \remark The order of the calls matters. Method on_csi() must be called before on_uci() or on_sch(). Otherwise, an -/// assertion is triggered. -class pusch_processor_result_notifier_adaptor : public pusch_processor_result_notifier -{ -public: - /// \brief Creates a PUSCH processor result notifier adaptor. - /// \param[in] notifier_ Upper physical layer result notifier. - /// \param[in] rnti_ User RNTI. - /// \param[in] slot_ Current slot. - /// \param[in] harq_id_ User HARQ process identifier. - /// \param[in] payload_ View to the data payload. - pusch_processor_result_notifier_adaptor(upper_phy_rx_results_notifier& notifier_, - uint16_t rnti_, - slot_point slot_, - unsigned harq_id_, - span payload_) : - notifier(notifier_), rnti(to_rnti(rnti_)), slot(slot_), harq_id(to_harq_id(harq_id_)), payload(payload_) - { - } - - // See interface for documentation. - void on_uci(const pusch_processor_result_control& uci) override - { - ul_pusch_results_control result; - result.rnti = rnti; - result.slot = slot; - result.csi = uci.csi; - - if (!uci.harq_ack.payload.empty()) { - result.harq_ack.emplace(uci.harq_ack); - } - - if (!uci.csi_part1.payload.empty()) { - result.csi1.emplace(uci.csi_part1); - } - - if (!uci.csi_part2.payload.empty()) { - result.csi2.emplace(uci.csi_part2); - } - - notifier.on_new_pusch_results_control(result); - } - - // See interface for documentation. - void on_sch(const pusch_processor_result_data& sch) override - { - ul_pusch_results_data result; - result.rnti = rnti; - result.slot = slot; - result.csi = sch.csi; - result.harq_id = harq_id; - result.decoder_result = sch.data; - result.payload = (sch.data.tb_crc_ok) ? payload : span(); - notifier.on_new_pusch_results_data(result); - - // Store the TB CRC okay flag. - tb_crc_ok = sch.data.tb_crc_ok; - } - - /// \brief Gets the TB CRC okay flag. - /// \return True if the transport block CRC passed, otherwise False. - bool get_tb_crc_ok() const { return tb_crc_ok; } - -private: - upper_phy_rx_results_notifier& notifier; - rnti_t rnti; - slot_point slot; - harq_id_t harq_id; - span payload; - bool tb_crc_ok = false; -}; - -} // namespace - /// \brief Returns a PRACH detector slot configuration using the given PRACH buffer context. static prach_detector::configuration get_prach_dectector_config_from_prach_context(const prach_buffer_context& context) { @@ -128,11 +47,19 @@ static prach_detector::configuration get_prach_dectector_config_from_prach_conte uplink_processor_impl::uplink_processor_impl(std::unique_ptr prach_, std::unique_ptr pusch_proc_, std::unique_ptr pucch_proc_) : - prach(std::move(prach_)), pusch_proc(std::move(pusch_proc_)), pucch_proc(std::move(pucch_proc_)) + free_pusch_adaptors(max_nof_pusch_notifier_adaptors), + prach(std::move(prach_)), + pusch_proc(std::move(pusch_proc_)), + pucch_proc(std::move(pucch_proc_)), + logger(srslog::fetch_basic_logger("PHY", true)) { srsran_assert(prach, "A valid PRACH detector must be provided"); srsran_assert(pusch_proc, "A valid PUSCH processor must be provided"); srsran_assert(pucch_proc, "A valid PUCCH processor must be provided"); + + for (unsigned i = 0; i != max_nof_pusch_notifier_adaptors; ++i) { + pusch_adaptors.emplace_back(detail::pusch_processor_result_notifier_adaptor(free_pusch_adaptors)); + } } void uplink_processor_impl::process_prach(upper_phy_rx_results_notifier& notifier, @@ -153,16 +80,22 @@ void uplink_processor_impl::process_pusch(span dat const resource_grid_reader& grid, const uplink_processor::pusch_pdu& pdu) { - // Creates a PUSCH processor result notifier adaptor. - pusch_processor_result_notifier_adaptor processor_notifier(notifier, pdu.pdu.rnti, pdu.pdu.slot, pdu.harq_id, data); + // Pop an adaptor identifier. + optional adaptor_id = free_pusch_adaptors.try_pop(); + if (!adaptor_id.has_value()) { + logger.set_context(pdu.pdu.slot.sfn(), pdu.pdu.slot.slot_index()); + logger.warning("UL rnti={:#x} h_id={}: insufficient number of PUSCH notifier adaptor. Dropping PDU.", + pdu.pdu.rnti, + pdu.harq_id); + return; + } - // Processes PUSCH. - pusch_proc->process(data, softbuffer.get(), processor_notifier, grid, pdu.pdu); + // Configure adaptor. + pusch_processor_result_notifier& processor_notifier = pusch_adaptors[adaptor_id.value()].configure( + notifier, to_rnti(pdu.pdu.rnti), pdu.pdu.slot, to_harq_id(pdu.harq_id), data); - // Release softbuffer if the TB CRC passed. - if (processor_notifier.get_tb_crc_ok()) { - softbuffer.release(); - } + // Process PUSCH. + pusch_proc->process(data, std::move(softbuffer), processor_notifier, grid, pdu.pdu); } void uplink_processor_impl::process_pucch(upper_phy_rx_results_notifier& notifier, diff --git a/lib/phy/upper/uplink_processor_impl.h b/lib/phy/upper/uplink_processor_impl.h index dc219086d2..86218e4cb5 100644 --- a/lib/phy/upper/uplink_processor_impl.h +++ b/lib/phy/upper/uplink_processor_impl.h @@ -22,10 +22,118 @@ #pragma once +#include "srsran/adt/concurrent_queue.h" +#include "srsran/phy/upper/channel_processors/pusch/pusch_processor_result_notifier.h" #include "srsran/phy/upper/uplink_processor.h" +#include "srsran/phy/upper/upper_phy_rx_results_notifier.h" namespace srsran { +namespace detail { + +/// Concurrent queue type used for storing the PUSCH processor result notifier adaptors. +using free_adaptor_queue = + concurrent_queue; + +/// \brief Adapts the PUSCH processor result notifier to the upper PHY receive results notifier. +/// +/// It collects the Channel State Information (CSI), control and data decoding information from a PUSCH processor object +/// and notifies them through the upper PHY notification interface. +/// +/// \remark The order of the calls matters. Method on_csi() must be called before on_uci() or on_sch(). Otherwise, an +/// assertion is triggered. +class pusch_processor_result_notifier_adaptor : private pusch_processor_result_notifier +{ +public: + /// \brief Constructs an adaptor. + /// + /// The PUSCH adaptor queue identifier is set to the current size of the queue. + /// + /// \param[in] queue_ Free adaptor queue instance. + pusch_processor_result_notifier_adaptor(free_adaptor_queue& queue_) : queue(queue_), queue_identifier(queue.size()) + { + queue.push_blocking(queue_identifier); + } + + /// \brief Creates a PUSCH processor result notifier adaptor. + /// \param[in] notifier_ Upper physical layer result notifier. + /// \param[in] rnti_ User RNTI. + /// \param[in] slot_ Current slot. + /// \param[in] harq_id_ User HARQ process identifier. + /// \param[in] payload_ View to the data payload. + pusch_processor_result_notifier& configure(upper_phy_rx_results_notifier& notifier_, + rnti_t rnti_, + slot_point slot_, + harq_id_t harq_id_, + span payload_) + { + notifier = ¬ifier_; + rnti = rnti_; + slot = slot_; + harq_id = harq_id_; + payload = payload_; + return *this; + } + +private: + // See interface for documentation. + void on_uci(const pusch_processor_result_control& uci) override + { + srsran_assert(notifier != nullptr, "Invalid notifier."); + + ul_pusch_results_control result; + result.rnti = rnti; + result.slot = slot; + result.csi = uci.csi; + + if (!uci.harq_ack.payload.empty()) { + result.harq_ack.emplace(uci.harq_ack); + } + + if (!uci.csi_part1.payload.empty()) { + result.csi1.emplace(uci.csi_part1); + } + + if (!uci.csi_part2.payload.empty()) { + result.csi2.emplace(uci.csi_part2); + } + + notifier->on_new_pusch_results_control(result); + } + + // See interface for documentation. + void on_sch(const pusch_processor_result_data& sch) override + { + // Gets the notifier and invalidates. It makes sure the notifier is no longer available after the exchange. + upper_phy_rx_results_notifier* notifier_ = nullptr; + std::exchange(notifier_, notifier); + srsran_assert(notifier_ != nullptr, "Invalid notifier."); + + // Notify the completion of PUSCH data. + ul_pusch_results_data result; + result.rnti = rnti; + result.slot = slot; + result.csi = sch.csi; + result.harq_id = harq_id; + result.decoder_result = sch.data; + result.payload = (sch.data.tb_crc_ok) ? payload : span(); + notifier_->on_new_pusch_results_data(result); + + // Return the adaptor identifier to the queue. + queue.push_blocking(queue_identifier); + } + + free_adaptor_queue& queue; + unsigned queue_identifier; + upper_phy_rx_results_notifier* notifier = nullptr; + rnti_t rnti = {}; + slot_point slot = {}; + harq_id_t harq_id = {}; + span payload = {}; +}; + +} // namespace detail + class task_executor; /// \brief Uplink processor implementation. @@ -57,12 +165,20 @@ class uplink_processor_impl : public uplink_processor const pucch_pdu& pdu) override; private: + /// Maximum number of PUSCH notifier adaptors. + static constexpr unsigned max_nof_pusch_notifier_adaptors = 1024; + /// Pool of adaptors. + std::vector pusch_adaptors; + /// Queue of free adaptors. + detail::free_adaptor_queue free_pusch_adaptors; /// PRACH detector. std::unique_ptr prach; /// PUSCH processor. std::unique_ptr pusch_proc; /// PUCCH processor. std::unique_ptr pucch_proc; + /// General PHY logger, used to notify pool failures. + srslog::basic_logger& logger; }; } // namespace srsran diff --git a/lib/phy/upper/upper_phy_factories.cpp b/lib/phy/upper/upper_phy_factories.cpp index 225585b0d2..e4a16a3ca6 100644 --- a/lib/phy/upper/upper_phy_factories.cpp +++ b/lib/phy/upper/upper_phy_factories.cpp @@ -467,6 +467,9 @@ static std::shared_ptr create_ul_processor_factory(con "Invalid LDPC Rate Dematcher factory of type {}.", config.ldpc_rate_dematcher_type); decoder_config.segmenter_factory = create_ldpc_segmenter_rx_factory_sw(); + report_fatal_error_if_not(decoder_config.segmenter_factory, "Invalid LDPC Rx segmenter factory."); + decoder_config.nof_pusch_decoder_threads = config.nof_pusch_decoder_threads; + decoder_config.executor = config.pusch_decoder_executor; std::shared_ptr short_block_det_factory = create_short_block_detector_factory_sw(); report_fatal_error_if_not(short_block_det_factory, "Invalid short block detector factory."); @@ -506,6 +509,10 @@ static std::shared_ptr create_ul_processor_factory(con std::shared_ptr pusch_factory = create_pusch_processor_factory_sw(pusch_config); report_fatal_error_if_not(pusch_factory, "Invalid PUSCH processor factory."); + // Create PUSCH processor pool factory. + pusch_factory = create_pusch_processor_pool(std::move(pusch_factory), config.max_pusch_concurrency); + report_fatal_error_if_not(pusch_factory, "Invalid PUSCH processor pool factory."); + std::shared_ptr lpg_factory = create_low_papr_sequence_generator_sw_factory(); std::shared_ptr lpc_factory = create_low_papr_sequence_collection_sw_factory(lpg_factory); @@ -531,7 +538,7 @@ static std::shared_ptr create_ul_processor_factory(con channel_estimate::channel_estimate_dimensions channel_estimate_dimensions; channel_estimate_dimensions.nof_tx_layers = 1; - channel_estimate_dimensions.nof_rx_ports = 1; + channel_estimate_dimensions.nof_rx_ports = config.nof_rx_ports; channel_estimate_dimensions.nof_symbols = MAX_NSYMB_PER_SLOT; channel_estimate_dimensions.nof_prb = config.ul_bw_rb; @@ -780,8 +787,9 @@ srsran::create_downlink_processor_factory_sw(const downlink_processor_factory_sw report_fatal_error_if_not(pdsch_processor_config.pdsch_codeblock_task_executor != nullptr, "Invalid codeblock executor."); + // Create concurrent PDSCH processor factory base. pdsch_proc_factory = - create_pdsch_concurrent_processor_factory_sw(ldpc_seg_tx_factory, + create_pdsch_concurrent_processor_factory_sw(crc_calc_factory, ldpc_enc_factory, ldpc_rm_factory, prg_factory, @@ -789,6 +797,11 @@ srsran::create_downlink_processor_factory_sw(const downlink_processor_factory_sw dmrs_pdsch_proc_factory, *pdsch_processor_config.pdsch_codeblock_task_executor, pdsch_processor_config.nof_pdsch_codeblock_threads); + report_fatal_error_if_not(pdsch_proc_factory, "Invalid PDSCH processor factory."); + + // Wrap PDSCH processor factory with a pool to allow concurrent execution. + pdsch_proc_factory = + create_pdsch_processor_pool(std::move(pdsch_proc_factory), pdsch_processor_config.max_nof_simultaneous_pdsch); } else if (variant_holds_alternative(config.pdsch_processor)) { pdsch_proc_factory = create_pdsch_lite_processor_factory_sw( ldpc_seg_tx_factory, ldpc_enc_factory, ldpc_rm_factory, prg_factory, mod_factory, dmrs_pdsch_proc_factory); diff --git a/lib/phy/upper/upper_phy_rx_symbol_handler_impl.cpp b/lib/phy/upper/upper_phy_rx_symbol_handler_impl.cpp index a5165ba217..a0f5741382 100644 --- a/lib/phy/upper/upper_phy_rx_symbol_handler_impl.cpp +++ b/lib/phy/upper/upper_phy_rx_symbol_handler_impl.cpp @@ -142,7 +142,4 @@ void upper_phy_rx_symbol_handler_impl::process_pusch(const uplink_processor::pus ul_processor.process_pusch(payload, std::move(buffer), rx_results_notifier, grid, pdu); return; } - - logger.set_context(pdu.pdu.slot.sfn(), pdu.pdu.slot.slot_index()); - logger.warning("Could not reserve a softbuffer for PUSCH PDU with RNTI={}, HARQ={}", id.rnti, id.harq_ack_id); } diff --git a/lib/ran/csi_report/csi_report_config_helpers.cpp b/lib/ran/csi_report/csi_report_config_helpers.cpp index b031a97c46..c4936128d9 100644 --- a/lib/ran/csi_report/csi_report_config_helpers.cpp +++ b/lib/ran/csi_report/csi_report_config_helpers.cpp @@ -21,7 +21,7 @@ */ #include "srsran/ran/csi_report/csi_report_config_helpers.h" -#include "csi_report_on_puxch_helpers.h" +#include "srsran/ran/csi_report/csi_report_on_puxch_utils.h" using namespace srsran; diff --git a/lib/ran/csi_report/csi_report_on_pucch_helpers.cpp b/lib/ran/csi_report/csi_report_on_pucch_helpers.cpp index a892a67951..fa8f89a888 100644 --- a/lib/ran/csi_report/csi_report_on_pucch_helpers.cpp +++ b/lib/ran/csi_report/csi_report_on_pucch_helpers.cpp @@ -25,6 +25,7 @@ #include "srsran/ran/csi_report/csi_report_config_helpers.h" #include "srsran/ran/csi_report/csi_report_configuration.h" #include "srsran/ran/csi_report/csi_report_data.h" +#include "srsran/ran/csi_report/csi_report_on_puxch_utils.h" #include "srsran/support/error_handling.h" using namespace srsran; @@ -249,4 +250,4 @@ csi_report_data srsran::csi_report_unpack_pucch(const csi_report_packed& packed, default: report_error("Invalid CSI report quantities."); } -} \ No newline at end of file +} diff --git a/lib/ran/csi_report/csi_report_on_pusch_helpers.cpp b/lib/ran/csi_report/csi_report_on_pusch_helpers.cpp index ac5c7d1d1f..a705b72604 100644 --- a/lib/ran/csi_report/csi_report_on_pusch_helpers.cpp +++ b/lib/ran/csi_report/csi_report_on_pusch_helpers.cpp @@ -24,6 +24,7 @@ #include "csi_report_on_puxch_helpers.h" #include "srsran/adt/interval.h" #include "srsran/ran/csi_report/csi_report_config_helpers.h" +#include "srsran/ran/csi_report/csi_report_on_puxch_utils.h" #include "srsran/support/error_handling.h" using namespace srsran; diff --git a/lib/ran/csi_report/csi_report_on_puxch_helpers.cpp b/lib/ran/csi_report/csi_report_on_puxch_helpers.cpp index a4ac18ed93..3686fa194a 100644 --- a/lib/ran/csi_report/csi_report_on_puxch_helpers.cpp +++ b/lib/ran/csi_report/csi_report_on_puxch_helpers.cpp @@ -21,6 +21,7 @@ */ #include "csi_report_on_puxch_helpers.h" #include "srsran/adt/interval.h" +#include "srsran/ran/csi_report/csi_report_on_puxch_utils.h" #include "srsran/support/error_handling.h" using namespace srsran; @@ -284,4 +285,4 @@ csi_report_data::ri_type srsran::csi_report_unpack_ri(const csi_report_packed& ri = ri_restriction.get_bit_positions()[ri] + 1; } return ri; -} \ No newline at end of file +} diff --git a/lib/ran/csi_report/csi_report_on_puxch_helpers.h b/lib/ran/csi_report/csi_report_on_puxch_helpers.h index cac7f2c11f..292d75436d 100644 --- a/lib/ran/csi_report/csi_report_on_puxch_helpers.h +++ b/lib/ran/csi_report/csi_report_on_puxch_helpers.h @@ -39,22 +39,6 @@ struct ri_li_cqi_cri_sizes { unsigned cri; }; -/// Gets the number of CSI-RS antenna ports from the PMI codebook type. -inline unsigned csi_report_get_nof_csi_rs_antenna_ports(pmi_codebook_type pmi_codebook) -{ - switch (pmi_codebook) { - case pmi_codebook_type::one: - return 1; - case pmi_codebook_type::two: - return 2; - case pmi_codebook_type::typeI_single_panel_4ports_mode1: - return 4; - case pmi_codebook_type::other: - default: - return 0; - } -} - /// Gets the RI, LI, wideband CQI, and CRI fields bit-width. ri_li_cqi_cri_sizes get_ri_li_cqi_cri_sizes(pmi_codebook_type pmi_codebook, ri_restriction_type ri_restriction, @@ -75,4 +59,4 @@ csi_report_unpack_pmi(const csi_report_packed& packed, pmi_codebook_type codeboo csi_report_data::ri_type csi_report_unpack_ri(const csi_report_packed& ri_packed, const ri_restriction_type& ri_restriction); -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/lib/rlc/rlc_am_pdu.cpp b/lib/rlc/rlc_am_pdu.cpp index 9df6c7300f..0bd4f1e7f1 100644 --- a/lib/rlc/rlc_am_pdu.cpp +++ b/lib/rlc/rlc_am_pdu.cpp @@ -64,23 +64,19 @@ bool rlc_am_status_pdu::can_be_merged(const rlc_am_status_nack& left, const rlc_ // cumulated NACK range must not exceed 255 uint16_t left_range = (left.has_nack_range ? left.nack_range : 1); uint16_t right_range = (right.has_nack_range ? right.nack_range : 1); - if (left_range + right_range > 255) { - return false; - } - - return true; + return left_range + right_range <= 255; } void rlc_am_status_pdu::push_nack(const rlc_am_status_nack& nack) { - if (nacks.size() == 0) { + if (nacks.empty()) { nacks.push_back(nack); packed_size += nack_size(nack); return; } rlc_am_status_nack& prev = nacks.back(); - if (can_be_merged(prev, nack) == false) { + if (!can_be_merged(prev, nack)) { nacks.push_back(nack); packed_size += nack_size(nack); return; @@ -91,8 +87,8 @@ void rlc_am_status_pdu::push_nack(const rlc_am_status_nack& nack) packed_size -= nack_size(prev); // enable and update NACK range - if (nack.has_nack_range == true) { - if (prev.has_nack_range == true) { + if (nack.has_nack_range) { + if (prev.has_nack_range) { // [NACK range][NACK range] prev.nack_range += nack.nack_range; } else { @@ -101,7 +97,7 @@ void rlc_am_status_pdu::push_nack(const rlc_am_status_nack& nack) prev.has_nack_range = true; } } else { - if (prev.has_nack_range == true) { + if (prev.has_nack_range) { // [NACK range][NACK SDU] prev.nack_range++; } else { @@ -112,8 +108,8 @@ void rlc_am_status_pdu::push_nack(const rlc_am_status_nack& nack) } // enable and update segment offsets (if required) - if (nack.has_so == true) { - if (prev.has_so == false) { + if (nack.has_so) { + if (!prev.has_so) { // [NACK SDU][NACK segm] prev.has_so = true; prev.so_start = 0; @@ -121,7 +117,7 @@ void rlc_am_status_pdu::push_nack(const rlc_am_status_nack& nack) // [NACK SDU][NACK segm] or [NACK segm][NACK segm] prev.so_end = nack.so_end; } else { - if (prev.has_so == true) { + if (prev.has_so) { // [NACK segm][NACK SDU] prev.so_end = rlc_am_status_nack::so_end_of_sdu; } @@ -148,7 +144,7 @@ bool rlc_am_status_pdu::trim(uint32_t max_packed_size) // see TS 38.322 Sec. 5.3.4: // "set the ACK_SN to the SN of the next not received RLC SDU // which is not indicated as missing in the resulting STATUS PDU." - while (nacks.size() > 0 && (max_packed_size < packed_size || nacks.back().nack_sn == ack_sn)) { + while (!nacks.empty() && (max_packed_size < packed_size || nacks.back().nack_sn == ack_sn)) { packed_size -= nack_size(nacks.back()); ack_sn = nacks.back().nack_sn; nacks.pop_back(); @@ -437,23 +433,36 @@ bool rlc_am_status_pdu::pack_12bit(byte_buffer& pdu) const byte_buffer_writer hdr_writer = hdr_buf; // fixed header part - hdr_writer.append(0U); // 1 bit D/C field and 3 bit CPT are all zero + // 1 bit D/C field and 3 bit CPT are all zero + if (not hdr_writer.append(0U)) { + return false; + } // write first 4 bit of ACK_SN hdr_writer.back() |= (ack_sn >> 8U) & 0x0fU; // 4 bits ACK_SN - hdr_writer.append(ack_sn & 0xffU); // remaining 8 bits of SN + if (not hdr_writer.append(ack_sn & 0xffU)) { // remaining 8 bits of SN + return false; + } // write E1 flag in octet 3 - if (nacks.size() > 0) { - hdr_writer.append(0x80U); + if (!nacks.empty()) { + if (not hdr_writer.append(0x80U)) { + return false; + } } else { - hdr_writer.append(0x00U); + if (not hdr_writer.append(0x00U)) { + return false; + } } - if (nacks.size() > 0) { + if (!nacks.empty()) { for (uint32_t i = 0; i < nacks.size(); i++) { - hdr_writer.append((nacks[i].nack_sn >> 4U) & 0xffU); // upper 8 bits of SN - hdr_writer.append((nacks[i].nack_sn & 0x0fU) << 4U); // lower 4 bits of SN + if (not hdr_writer.append((nacks[i].nack_sn >> 4U) & 0xffU)) { // upper 8 bits of SN + return false; + } + if (not hdr_writer.append((nacks[i].nack_sn & 0x0fU) << 4U)) { // lower 4 bits of SN + return false; + } if (i < (uint32_t)(nacks.size() - 1)) { hdr_writer.back() |= 0x08U; // Set E1 @@ -466,13 +475,23 @@ bool rlc_am_status_pdu::pack_12bit(byte_buffer& pdu) const } if (nacks[i].has_so) { - hdr_writer.append(nacks[i].so_start >> 8U); - hdr_writer.append(nacks[i].so_start); - hdr_writer.append(nacks[i].so_end >> 8U); - hdr_writer.append(nacks[i].so_end); + if (not hdr_writer.append(nacks[i].so_start >> 8U)) { + return false; + } + if (not hdr_writer.append(nacks[i].so_start)) { + return false; + } + if (not hdr_writer.append(nacks[i].so_end >> 8U)) { + return false; + } + if (not hdr_writer.append(nacks[i].so_end)) { + return false; + } } if (nacks[i].has_nack_range) { - hdr_writer.append(nacks[i].nack_range); + if (not hdr_writer.append(nacks[i].nack_range)) { + return false; + } } } } @@ -486,22 +505,33 @@ bool rlc_am_status_pdu::pack_18bit(byte_buffer& pdu) const byte_buffer_writer hdr_writer = hdr_buf; // fixed header part - hdr_writer.append(0U); // 1 bit D/C field and 3 bit CPT are all zero - - hdr_writer.back() |= (ack_sn >> 14U) & 0x0fU; // upper 4 bits of SN - hdr_writer.append((ack_sn >> 6U) & 0xffU); // center 8 bits of SN - hdr_writer.append((ack_sn << 2U) & 0xfcU); // lower 6 bits of SN + if (not hdr_writer.append(0U)) { // 1 bit D/C field and 3 bit CPT are all zero + return false; + } + hdr_writer.back() |= (ack_sn >> 14U) & 0x0fU; // upper 4 bits of SN + if (not hdr_writer.append((ack_sn >> 6U) & 0xffU)) { // center 8 bits of SN + return false; + } + if (not hdr_writer.append((ack_sn << 2U) & 0xfcU)) { // lower 6 bits of SN + return false; + } // set E1 flag if necessary - if (nacks.size() > 0) { + if (!nacks.empty()) { hdr_writer.back() |= 0x02; } - if (nacks.size() > 0) { + if (!nacks.empty()) { for (uint32_t i = 0; i < nacks.size(); i++) { - hdr_writer.append((nacks[i].nack_sn >> 10) & 0xffU); // upper 8 bits of SN - hdr_writer.append((nacks[i].nack_sn >> 2) & 0xffU); // center 8 bits of SN - hdr_writer.append((nacks[i].nack_sn << 6) & 0xc0U); // lower 2 bits of SN + if (not hdr_writer.append((nacks[i].nack_sn >> 10) & 0xffU)) { // upper 8 bits of SN + return false; + } + if (not hdr_writer.append((nacks[i].nack_sn >> 2) & 0xffU)) { // center 8 bits of SN + return false; + } + if (not hdr_writer.append((nacks[i].nack_sn << 6) & 0xc0U)) { // lower 2 bits of SN + return false; + } if (i < (uint32_t)(nacks.size() - 1)) { hdr_writer.back() |= 0x20U; // Set E1 @@ -514,13 +544,23 @@ bool rlc_am_status_pdu::pack_18bit(byte_buffer& pdu) const } if (nacks[i].has_so) { - hdr_writer.append(nacks[i].so_start >> 8); - hdr_writer.append(nacks[i].so_start); - hdr_writer.append(nacks[i].so_end >> 8); - hdr_writer.append(nacks[i].so_end); + if (not hdr_writer.append(nacks[i].so_start >> 8)) { + return false; + } + if (not hdr_writer.append(nacks[i].so_start)) { + return false; + } + if (not hdr_writer.append(nacks[i].so_end >> 8)) { + return false; + } + if (not hdr_writer.append(nacks[i].so_end)) { + return false; + } } if (nacks[i].has_nack_range) { - hdr_writer.append(nacks[i].nack_range); + if (not hdr_writer.append(nacks[i].nack_range)) { + return false; + } } } } diff --git a/lib/rlc/rlc_am_pdu.h b/lib/rlc/rlc_am_pdu.h index c424398e2f..920b17c3d5 100644 --- a/lib/rlc/rlc_am_pdu.h +++ b/lib/rlc/rlc_am_pdu.h @@ -161,12 +161,12 @@ class rlc_am_status_pdu /// \brief Write the RLC AM status PDU to a PDU buffer and eets the length of the generate PDU accordingly /// \param[out] pdu A reference to a byte_buffer /// \return true if PDU was written successfully, false otherwise - bool pack(byte_buffer& pdu) const; + SRSRAN_NODISCARD bool pack(byte_buffer& pdu) const; /// \brief Read a RLC AM status PDU from a PDU buffer view /// \param pdu A reference to a byte_buffer_view /// \return true if PDU was read successfully, false otherwise - bool unpack(const byte_buffer_view& pdu); + SRSRAN_NODISCARD bool unpack(const byte_buffer_view& pdu); /// \brief Checks if a PDU buffer view contains a control PDU /// \param pdu A reference to a byte_buffer_view @@ -178,7 +178,7 @@ class rlc_am_status_pdu * Header pack/unpack helper functions * Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.4 ***************************************************************************/ -inline bool +inline SRSRAN_NODISCARD bool rlc_am_read_data_pdu_header(const byte_buffer_view& pdu, const rlc_am_sn_size sn_size, rlc_am_pdu_header* header) { byte_buffer_reader pdu_reader = pdu; @@ -248,33 +248,46 @@ rlc_am_read_data_pdu_header(const byte_buffer_view& pdu, const rlc_am_sn_size sn return true; } -inline void rlc_am_write_data_pdu_header(const rlc_am_pdu_header& header, byte_buffer& pdu) +inline SRSRAN_NODISCARD bool rlc_am_write_data_pdu_header(const rlc_am_pdu_header& header, byte_buffer& pdu) { byte_buffer hdr_buf; byte_buffer_writer hdr_writer = hdr_buf; // fixed header part - hdr_writer.append((to_number(header.dc) & 0x01U) << 7U); // 1 bit D/C field + if (not hdr_writer.append((to_number(header.dc) & 0x01U) << 7U)) { // 1 bit D/C field + return false; + } hdr_writer.back() |= (header.p & 0x01U) << 6U; // 1 bit P flag hdr_writer.back() |= (to_number(header.si) & 0x03U) << 4U; // 2 bits SI if (header.sn_size == rlc_am_sn_size::size12bits) { // write first 4 bit of SN hdr_writer.back() |= (header.sn >> 8U) & 0x0fU; // 4 bit SN - hdr_writer.append(header.sn & 0xffU); // remaining 8 bit of SN + if (not hdr_writer.append(header.sn & 0xffU)) { // remaining 8 bit of SN + return false; + } } else { // 18bit SN hdr_writer.back() |= (header.sn >> 16U) & 0x3U; // 2 bit SN - hdr_writer.append(header.sn >> 8U); // bit 3 - 10 of SN - hdr_writer.append(header.sn & 0xffU); // remaining 8 bit of SN + if (not hdr_writer.append(header.sn >> 8U)) { // bit 3 - 10 of SN + return false; + } + if (not hdr_writer.append(header.sn & 0xffU)) { // remaining 8 bit of SN + return false; + } } if (header.so != 0) { // write SO - hdr_writer.append(header.so >> 8U); // first part of SO - hdr_writer.append(header.so & 0xffU); // second part of SO + if (not hdr_writer.append(header.so >> 8U)) { // first part of SO + return false; + } + if (not hdr_writer.append(header.so & 0xffU)) { // second part of SO + return false; + } } pdu.prepend(std::move(hdr_buf)); + return true; } } // namespace srsran @@ -314,16 +327,13 @@ struct formatter { if (nack.has_nack_range) { if (nack.has_so) { return format_to(ctx.out(), "[{} {}:{} r{}]", nack.nack_sn, nack.so_start, nack.so_end, nack.nack_range); - } else { - return format_to(ctx.out(), "[{} r{}]", nack.nack_sn, nack.nack_range); - } - } else { - if (nack.has_so) { - return format_to(ctx.out(), "[{} {}:{}]", nack.nack_sn, nack.so_start, nack.so_end); - } else { - return format_to(ctx.out(), "[{}]", nack.nack_sn); } + return format_to(ctx.out(), "[{} r{}]", nack.nack_sn, nack.nack_range); + } + if (nack.has_so) { + return format_to(ctx.out(), "[{} {}:{}]", nack.nack_sn, nack.so_start, nack.so_end); } + return format_to(ctx.out(), "[{}]", nack.nack_sn); } }; @@ -341,7 +351,7 @@ struct formatter { { memory_buffer buffer; format_to(buffer, "ack_sn={} n_nack={}", status.ack_sn, status.get_nacks().size()); - if (status.get_nacks().size() > 0) { + if (!status.get_nacks().empty()) { format_to(buffer, " nack="); for (auto nack : status.get_nacks()) { format_to(buffer, "{}", nack); diff --git a/lib/rlc/rlc_rx_metrics_container.h b/lib/rlc/rlc_rx_metrics_container.h index ab66ce29f3..4aff1bcc37 100644 --- a/lib/rlc/rlc_rx_metrics_container.h +++ b/lib/rlc/rlc_rx_metrics_container.h @@ -69,8 +69,8 @@ class rlc_rx_metrics_container /// RLC AM specific metrics void metrics_add_ctrl_pdus(uint32_t num_ctrl_, uint32_t num_ctrl_pdu_bytes_) { - srsran_assert(metrics.mode == rlc_mode::am, "Wrong mode for AM metrics."); std::lock_guard lock(metrics_mutex); + srsran_assert(metrics.mode == rlc_mode::am, "Wrong mode for AM metrics."); metrics.mode_specific.am.num_ctrl_pdus += num_ctrl_; metrics.mode_specific.am.num_ctrl_pdu_bytes += num_ctrl_pdu_bytes_; } diff --git a/lib/rlc/rlc_sdu_queue.h b/lib/rlc/rlc_sdu_queue.h index 9aaf2c37be..7835996679 100644 --- a/lib/rlc/rlc_sdu_queue.h +++ b/lib/rlc/rlc_sdu_queue.h @@ -37,7 +37,7 @@ namespace srsran { class rlc_sdu_queue { public: - explicit rlc_sdu_queue(uint16_t capacity_ = 1024) : + explicit rlc_sdu_queue(uint16_t capacity_ = 4096) : capacity(capacity_), queue(capacity_, push_callback(unread_bytes, n_sdus), pop_callback(unread_bytes, n_sdus)) { } diff --git a/lib/rlc/rlc_tx_am_entity.cpp b/lib/rlc/rlc_tx_am_entity.cpp index 9d519c9d52..89acfbd884 100644 --- a/lib/rlc/rlc_tx_am_entity.cpp +++ b/lib/rlc/rlc_tx_am_entity.cpp @@ -77,7 +77,7 @@ void rlc_tx_am_entity::handle_sdu(rlc_sdu sdu) metrics.metrics_add_sdus(1, sdu_length); handle_changed_buffer_state(); } else { - logger.log_info("Dropped SDU. sdu_len={} pdcp_sn={} {}", sdu_length, sdu.pdcp_sn, sdu_queue); + logger.log_warning("Dropped SDU. sdu_len={} pdcp_sn={} {}", sdu_length, sdu.pdcp_sn, sdu_queue); metrics.metrics_add_lost_sdus(1); } } @@ -119,7 +119,10 @@ byte_buffer_chain rlc_tx_am_entity::pull_pdu(uint32_t grant_len) logger.log_debug("Trimmed status PDU. {}", status_pdu); } byte_buffer pdu; - status_pdu.pack(pdu); + if (not status_pdu.pack(pdu)) { + logger.log_error("Could not pack status pdu. {}", status_pdu); + return {}; + } logger.log_info(pdu.begin(), pdu.end(), "TX status PDU. pdu_len={} grant_len={}", pdu.length(), grant_len); // Update metrics @@ -141,11 +144,10 @@ byte_buffer_chain rlc_tx_am_entity::pull_pdu(uint32_t grant_len) if (sn_under_segmentation != INVALID_RLC_SN) { if (tx_window->has_sn(sn_under_segmentation)) { return build_continued_sdu_segment((*tx_window)[sn_under_segmentation], grant_len); - } else { - sn_under_segmentation = INVALID_RLC_SN; - logger.log_error("SDU under segmentation does not exist in tx_window. sn={}", sn_under_segmentation); - // attempt to send next SDU } + sn_under_segmentation = INVALID_RLC_SN; + logger.log_error("SDU under segmentation does not exist in tx_window. sn={}", sn_under_segmentation); + // attempt to send next SDU } // Check whether there is something to TX @@ -165,7 +167,7 @@ byte_buffer_chain rlc_tx_am_entity::build_new_pdu(uint32_t grant_len) } // do not build any more PDU if window is already full - if (tx_window->full()) { + if (is_tx_window_full()) { logger.log_warning("Cannot build data PDU, tx_window is full. grant_len={}", grant_len); return {}; } @@ -206,7 +208,10 @@ byte_buffer_chain rlc_tx_am_entity::build_new_pdu(uint32_t grant_len) // Pack header byte_buffer header_buf = {}; - rlc_am_write_data_pdu_header(hdr, header_buf); + if (not rlc_am_write_data_pdu_header(hdr, header_buf)) { + logger.log_error("Could not pack RLC header. hdr={}", hdr); + return {}; + } // Assemble PDU byte_buffer_chain pdu_buf = {}; @@ -261,7 +266,10 @@ byte_buffer_chain rlc_tx_am_entity::build_first_sdu_segment(rlc_tx_am_sdu_info& // Pack header byte_buffer header_buf = {}; - rlc_am_write_data_pdu_header(hdr, header_buf); + if (not rlc_am_write_data_pdu_header(hdr, header_buf)) { + logger.log_error("Could not pack RLC header. hdr={}", hdr); + return {}; + } // Assemble PDU byte_buffer_chain pdu_buf = {}; @@ -342,7 +350,10 @@ byte_buffer_chain rlc_tx_am_entity::build_continued_sdu_segment(rlc_tx_am_sdu_in // Pack header byte_buffer header_buf = {}; - rlc_am_write_data_pdu_header(hdr, header_buf); + if (not rlc_am_write_data_pdu_header(hdr, header_buf)) { + logger.log_error("Could not pack RLC header. hdr={}", hdr); + return {}; + } // Assemble PDU byte_buffer_chain pdu_buf = {}; @@ -465,7 +476,10 @@ byte_buffer_chain rlc_tx_am_entity::build_retx_pdu(uint32_t grant_len) // Pack header byte_buffer header_buf = {}; - rlc_am_write_data_pdu_header(hdr, header_buf); + if (not rlc_am_write_data_pdu_header(hdr, header_buf)) { + logger.log_error("Could not pack RLC header. hdr={}", hdr); + return {}; + } srsran_assert(header_buf.length() == expected_hdr_len, "RETX hdr_len={} differs from expected_hdr_len={}", header_buf.length(), @@ -848,7 +862,7 @@ uint8_t rlc_tx_am_entity::get_polling_bit(uint32_t sn, bool is_retx, uint32_t pa * - if no new RLC SDU can be transmitted after the transmission of the AMD PDU (e.g. due to window stalling); * - include a poll in the AMD PDU as described below. */ - if ((sdu_queue.is_empty() && retx_queue.empty() && sn_under_segmentation == INVALID_RLC_SN) || tx_window->full()) { + if ((sdu_queue.is_empty() && retx_queue.empty() && sn_under_segmentation == INVALID_RLC_SN) || is_tx_window_full()) { logger.log_debug("Setting poll bit due to empty buffers/inablity to TX. sn={} poll_sn={}", sn, st.poll_sn); poll = 1; } @@ -912,7 +926,7 @@ void rlc_tx_am_entity::on_expired_poll_retransmit_timer() * retransmission; or * - consider any RLC SDU which has not been positively acknowledged for retransmission. */ - if ((sdu_queue.is_empty() && retx_queue.empty() && sn_under_segmentation == INVALID_RLC_SN) || tx_window->full()) { + if ((sdu_queue.is_empty() && retx_queue.empty() && sn_under_segmentation == INVALID_RLC_SN) || is_tx_window_full()) { if (tx_window->empty()) { logger.log_info( "Poll retransmit timer expired, but the TX window is empty. {} tx_window_size={}", st, tx_window->size()); @@ -983,6 +997,12 @@ bool rlc_tx_am_entity::inside_tx_window(uint32_t sn) const return tx_mod_base(sn) < am_window_size; } +bool rlc_tx_am_entity::is_tx_window_full() const +{ + // TX window is full, or we reached our virtual max window size + return tx_window->full() || (cfg.max_window != 0 && tx_mod_base(st.tx_next) > cfg.max_window); +} + bool rlc_tx_am_entity::valid_ack_sn(uint32_t sn) const { // Tx_Next_Ack < SN <= TX_Next + AM_Window_Size diff --git a/lib/rlc/rlc_tx_am_entity.h b/lib/rlc/rlc_tx_am_entity.h index 19e8369828..8901a1206c 100644 --- a/lib/rlc/rlc_tx_am_entity.h +++ b/lib/rlc/rlc_tx_am_entity.h @@ -183,6 +183,15 @@ class rlc_tx_am_entity : public rlc_tx_entity, public rlc_tx_am_status_handler, /// \return true if sn is inside the TX window, false otherwise bool inside_tx_window(uint32_t sn) const; + /// \brief Checks if the TX window is currently full. + /// + /// Note "full" may be smaller than the full window size to try to limit + /// memory usage. A virtual full window configuration parameter can be used + /// to avoid many UEs with 2^18 windows full of PDUs. + /// + /// \return true if the window is full. + bool is_tx_window_full() const; + /// \brief This function is used to check if a received status report as a valid ACK_SN. /// /// ACK_SN may be equal to TX_NEXT + AM_Window_Size if the PDU with SN=TX_NEXT+AM_Window_Size has been received by the diff --git a/lib/rlc/rlc_tx_metrics_container.h b/lib/rlc/rlc_tx_metrics_container.h index 741e6a212d..fe1c92a641 100644 --- a/lib/rlc/rlc_tx_metrics_container.h +++ b/lib/rlc/rlc_tx_metrics_container.h @@ -34,7 +34,11 @@ class rlc_tx_metrics_container std::mutex metrics_mutex; public: - void metrics_set_mode(rlc_mode mode) { metrics.mode = mode; } + void metrics_set_mode(rlc_mode mode) + { + std::lock_guard lock(metrics_mutex); + metrics.mode = mode; + } void metrics_add_sdus(uint32_t num_sdus_, size_t num_sdu_bytes_) { @@ -71,25 +75,25 @@ class rlc_tx_metrics_container // TM specific metrics void metrics_add_small_alloc(uint32_t num_allocs_) { - srsran_assert(metrics.mode == rlc_mode::tm, "Wrong mode for TM metrics."); std::lock_guard lock(metrics_mutex); + srsran_assert(metrics.mode == rlc_mode::tm, "Wrong mode for TM metrics."); metrics.mode_specific.tm.num_small_allocs += num_allocs_; } // UM specific metrics void metrics_add_segment(uint32_t num_segments_) { + std::lock_guard lock(metrics_mutex); srsran_assert(metrics.mode == rlc_mode::um_bidir || metrics.mode == rlc_mode::um_unidir_dl, "Wrong mode for UM metrics."); - std::lock_guard lock(metrics_mutex); metrics.mode_specific.um.num_sdu_segments += num_segments_; } // AM specific metrics void metrics_add_retx_pdus(uint32_t num_retx_, uint32_t num_retx_pdu_bytes_) { - srsran_assert(metrics.mode == rlc_mode::am, "Wrong mode for AM metrics."); std::lock_guard lock(metrics_mutex); + srsran_assert(metrics.mode == rlc_mode::am, "Wrong mode for AM metrics."); metrics.mode_specific.am.num_retx_pdus += num_retx_; metrics.mode_specific.am.num_retx_pdu_bytes += num_retx_pdu_bytes_; metrics.num_pdus += num_retx_; @@ -98,8 +102,8 @@ class rlc_tx_metrics_container void metrics_add_ctrl_pdus(uint32_t num_ctrl_, uint32_t num_ctrl_pdu_bytes_) { - srsran_assert(metrics.mode == rlc_mode::am, "Wrong mode for AM metrics."); std::lock_guard lock(metrics_mutex); + srsran_assert(metrics.mode == rlc_mode::am, "Wrong mode for AM metrics."); metrics.mode_specific.am.num_ctrl_pdus += num_ctrl_; metrics.mode_specific.am.num_ctrl_pdu_bytes += num_ctrl_pdu_bytes_; metrics.num_pdus += num_ctrl_; diff --git a/lib/rlc/rlc_um_pdu.h b/lib/rlc/rlc_um_pdu.h index a7e3987634..1c0cd6d5b2 100644 --- a/lib/rlc/rlc_um_pdu.h +++ b/lib/rlc/rlc_um_pdu.h @@ -168,12 +168,14 @@ inline size_t rlc_um_nr_packed_length(const rlc_um_pdu_header& header) return len; } -inline void rlc_um_write_data_pdu_header(const rlc_um_pdu_header& header, byte_buffer& pdu) +inline bool rlc_um_write_data_pdu_header(const rlc_um_pdu_header& header, byte_buffer& pdu) { byte_buffer hdr_buf; byte_buffer_writer hdr_writer = hdr_buf; // write SI field - hdr_writer.append((to_number(header.si) & 0x03U) << 6U); // 2 bits SI + if (not hdr_writer.append((to_number(header.si) & 0x03U) << 6U)) { // 2 bits SI + return false; + } if (header.si == rlc_si_field::full_sdu) { // that's all @@ -183,16 +185,23 @@ inline void rlc_um_write_data_pdu_header(const rlc_um_pdu_header& header, byte_b hdr_writer.back() |= (header.sn & 0x3fU); // 6 bit SN } else { // 12bit SN - hdr_writer.back() |= (header.sn >> 8U) & 0xfU; // high part of SN (4 bit) - hdr_writer.append(header.sn & 0xffU); // remaining 8 bit SN + hdr_writer.back() |= (header.sn >> 8U) & 0xfU; // high part of SN (4 bit) + if (not hdr_writer.append(header.sn & 0xffU)) { // remaining 8 bit SN + return false; + } } if (header.so != 0) { // write SO - hdr_writer.append((header.so) >> 8U); // first part of SO - hdr_writer.append(header.so & 0xffU); // second part of SO + if (not hdr_writer.append((header.so) >> 8U)) { // first part of SO + return false; + } + if (not hdr_writer.append(header.so & 0xffU)) { // second part of SO + return false; + } } } pdu.prepend(std::move(hdr_buf)); + return true; } } // namespace srsran diff --git a/lib/rrc/rrc_du_impl.h b/lib/rrc/rrc_du_impl.h index 4cab1af657..6a49165301 100644 --- a/lib/rrc/rrc_du_impl.h +++ b/lib/rrc/rrc_du_impl.h @@ -49,11 +49,13 @@ class rrc_du_impl : public rrc_du_interface // rrc_du_ue_repository rrc_ue_interface* add_ue(up_resource_manager& up_resource_mng, rrc_ue_creation_message msg) override; - void remove_ue(ue_index_t ue_index) override; void release_ues() override; void handle_amf_connection() override; void handle_amf_connection_drop() override; + // rrc_ue_removal_handler + void remove_ue(ue_index_t ue_index) override; + // rrc_du_ue_manager bool is_rrc_connect_allowed() override; @@ -63,9 +65,14 @@ class rrc_du_impl : public rrc_du_interface return ue_db.at(ue_index).get(); } - rrc_du_cell_manager& get_rrc_du_cell_manager() override { return *this; } - rrc_du_ue_manager& get_rrc_du_ue_manager() override { return *this; } - rrc_du_ue_repository& get_rrc_du_ue_repository() override { return *this; } + // rrc_du_statistics_handler + size_t get_nof_ues() const override { return ue_db.size(); } + + rrc_du_cell_manager& get_rrc_du_cell_manager() override { return *this; } + rrc_du_ue_manager& get_rrc_du_ue_manager() override { return *this; } + rrc_du_ue_repository& get_rrc_du_ue_repository() override { return *this; } + rrc_ue_removal_handler& get_rrc_ue_removal_handler() override { return *this; } + rrc_du_statistics_handler& get_rrc_du_statistics_handler() override { return *this; } private: // helpers diff --git a/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.cpp b/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.cpp index bc85f510dd..a44569a1dd 100644 --- a/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.cpp +++ b/lib/rrc/ue/procedures/rrc_reconfiguration_procedure.cpp @@ -70,11 +70,10 @@ void rrc_reconfiguration_procedure::operator()(coro_context>& c CORO_AWAIT(transaction); if (transaction.has_response()) { - logger.debug("ue={} \"{}\" finished successfull.", context.ue_index, name()); + logger.debug("ue={} \"{}\" finished successfully.", context.ue_index, name()); procedure_result = true; } else { - logger.debug("ue={} \"{}\" timed out after {}ms", context.ue_index, name(), context.cfg.rrc_procedure_timeout_ms); - rrc_ue.on_ue_delete_request(cause_protocol_t::unspecified); // delete UE context if reconfig fails + logger.warning("ue={} \"{}\" timed out after {}ms", context.ue_index, name(), context.cfg.rrc_procedure_timeout_ms); } logger.debug("ue={} \"{}\" finalized.", context.ue_index, name()); diff --git a/lib/rrc/ue/procedures/rrc_reestablishment_procedure.cpp b/lib/rrc/ue/procedures/rrc_reestablishment_procedure.cpp index 257522eaa9..90d582e510 100644 --- a/lib/rrc/ue/procedures/rrc_reestablishment_procedure.cpp +++ b/lib/rrc/ue/procedures/rrc_reestablishment_procedure.cpp @@ -63,11 +63,77 @@ void rrc_reestablishment_procedure::operator()(coro_context>& c { CORO_BEGIN(ctx); - logger.debug("ue={} old_ue={}: \"{}\" initialized.", context.ue_index, reestablishment_context.ue_index, name()); + logger.debug("ue={} old_ue={}: \"{}\" initialized", context.ue_index, reestablishment_context.ue_index, name()); - // Get the reestablishment context of the UE and verify the security context - if (!get_and_verify_reestablishment_context() or context.cfg.force_reestablishment_fallback) { - logger.debug("ue={} old_ue={} RRCReestablishmentRequest rejected, starting RRC Setup Procedure.", + // Verify if we are in conditions for a Reestablishment, or should opt for a RRC Setup. + if (is_reestablishment_rejected()) { + CORO_AWAIT(handle_rrc_reestablishment_fallback()); + CORO_EARLY_RETURN(); + } + + // Transfer old UE context to new UE context. If it fails, resort to fallback. + CORO_AWAIT_VALUE(context_transfer_success, + cu_cp_notifier.on_ue_transfer_required(context.ue_index, reestablishment_context.ue_index)); + if (not context_transfer_success) { + CORO_AWAIT(handle_rrc_reestablishment_fallback()); + CORO_EARLY_RETURN(); + } + + // Accept RRC Reestablishment Request by sending RRC Reestablishment + // Note: From this point we should guarantee that a Reestablishment will be performed. + + // transfer reestablishment context and update security keys + transfer_reestablishment_context_and_update_keys(); + + // create SRB1 + create_srb1(); + + // create new transaction for RRC Reestablishment + transaction = + event_mng.transactions.create_transaction(std::chrono::milliseconds(context.cfg.rrc_procedure_timeout_ms)); + + // send RRC Reestablishment to UE + send_rrc_reestablishment(); + + // Await UE response + CORO_AWAIT(transaction); + + if (transaction.has_response()) { + context.state = rrc_state::connected; + + // Notify DU Processor to start a Reestablishment Context Modification Routine + CORO_AWAIT_VALUE(context_modification_success, + du_processor_notifier.on_rrc_reestablishment_context_modification_required(context.ue_index)); + + // trigger UE context release at AMF in case of failure + if (not context_modification_success) { + logger.debug("ue={} old_ue={}: \"{}\" failed. Requesting UE context release", + context.ue_index, + reestablishment_context.ue_index, + name()); + // Release the old UE + send_ue_context_release_request(context.ue_index); + } else { + logger.debug("ue={} old_ue={}: \"{}\" finalized", context.ue_index, reestablishment_context.ue_index, name()); + } + + } else { + logger.warning("ue={} \"{}\" timed out after {}ms", context.ue_index, name(), context.cfg.rrc_procedure_timeout_ms); + logger.debug("ue={} old_ue={}: \"{}\" failed", context.ue_index, reestablishment_context.ue_index, name()); + } + + // Notify CU-CP to remove the old UE + cu_cp_notifier.on_ue_removal_required(reestablishment_context.ue_index); + + CORO_RETURN(); +} + +async_task rrc_reestablishment_procedure::handle_rrc_reestablishment_fallback() +{ + return launch_async([this](coro_context>& ctx) { + CORO_BEGIN(ctx); + + logger.debug("ue={} old_ue={} RRCReestablishmentRequest rejected, starting RRC Setup Procedure", context.ue_index, reestablishment_context.ue_index); @@ -81,62 +147,23 @@ void rrc_reestablishment_procedure::operator()(coro_context>& c event_mng, logger)); - if (reestablishment_context.ue_index != ue_index_t::invalid or !reestablishment_context.old_ue_fully_attached) { - // Release the old UE + if (reestablishment_context.ue_index != ue_index_t::invalid and !reestablishment_context.old_ue_fully_attached) { + // The UE exists but still has not established an SRB2 and DRB. Request the release of the old UE. + logger.debug("ue={} old_ue={} Old UE was not fully attached yet. Requesting UE context release", + context.ue_index, + reestablishment_context.ue_index); send_ue_context_release_request(reestablishment_context.ue_index); } - } else { - // Accept RRC Reestablishment Request by sending RRC Reestablishment - - // transfer reestablishment context and update security keys - transfer_reestablishment_context_and_update_keys(); - - // create SRB1 - create_srb1(); - - // create new transaction for RRC Reestablishment - transaction = - event_mng.transactions.create_transaction(std::chrono::milliseconds(context.cfg.rrc_procedure_timeout_ms)); - - // send RRC Reestablishment to UE - send_rrc_reestablishment(); - - // Notify CU-CP to transfer and remove UE contexts - cu_cp_notifier.on_ue_transfer_required(context.ue_index, reestablishment_context.ue_index); - - // Await UE response - CORO_AWAIT(transaction); - - if (transaction.has_response()) { - context.state = rrc_state::connected; - - // Notify DU Processor to start a Reestablishment Context Modification Routine - CORO_AWAIT_VALUE(context_modification_success, - du_processor_notifier.on_rrc_reestablishment_context_modification_required(context.ue_index)); - - // trigger UE context release at AMF in case of failure - if (not context_modification_success) { - // Release the old UE - send_ue_context_release_request(context.ue_index); - logger.debug("ue={} old_ue={}: \"{}\" failed.", context.ue_index, reestablishment_context.ue_index, name()); - } else { - logger.debug("ue={} old_ue={}: \"{}\" finalized.", context.ue_index, reestablishment_context.ue_index, name()); - } - - } else { - logger.debug("ue={} \"{}\" timed out after {}ms", context.ue_index, name(), context.cfg.rrc_procedure_timeout_ms); - logger.debug("ue={} old_ue={}: \"{}\" failed.", context.ue_index, reestablishment_context.ue_index, name()); - } - } - CORO_RETURN(); + CORO_RETURN(); + }); } bool rrc_reestablishment_procedure::get_and_verify_reestablishment_context() { bool security_context_valid = false; - // Notifiy CU-CP about the RRC Reestablishment Request to get old RRC UE context + // Notify CU-CP about the RRC Reestablishment Request to get old RRC UE context reestablishment_context = cu_cp_notifier.on_rrc_reestablishment_request(reestablishment_request.rrc_reest_request.ue_id.pci, to_rnti(reestablishment_request.rrc_reest_request.ue_id.c_rnti), @@ -144,7 +171,7 @@ bool rrc_reestablishment_procedure::get_and_verify_reestablishment_context() // check if reestablishment context exists if (reestablishment_context.ue_index == ue_index_t::invalid) { - logger.debug("Reestablishment context for UE with pci={} and rnti={:#04x} not found.", + logger.debug("Reestablishment context for UE with pci={} and rnti={:#04x} not found", reestablishment_request.rrc_reest_request.ue_id.pci, to_rnti(reestablishment_request.rrc_reest_request.ue_id.c_rnti)); } else { @@ -155,6 +182,11 @@ bool rrc_reestablishment_procedure::get_and_verify_reestablishment_context() return security_context_valid; } +bool rrc_reestablishment_procedure::is_reestablishment_rejected() +{ + return context.cfg.force_reestablishment_fallback or !get_and_verify_reestablishment_context(); +} + bool rrc_reestablishment_procedure::verify_security_context() { bool valid = false; @@ -175,7 +207,7 @@ bool rrc_reestablishment_procedure::verify_security_context() logger.debug(var_short_mac_input_packed.begin(), var_short_mac_input_packed.end(), - "Packed varShortMAC-Input. Source PCI={}, Target Cell-Id={}, Source C-RNTI={:#04x}.", + "Packed varShortMAC-Input. Source PCI={}, Target Cell-Id={}, Source C-RNTI={:#04x}", var_short_mac_input.source_pci, var_short_mac_input.target_cell_id.to_number(), var_short_mac_input.source_c_rnti); @@ -185,10 +217,9 @@ bool rrc_reestablishment_procedure::verify_security_context() security::sec_as_config source_as_config = reestablishment_context.sec_context.get_as_config(security::sec_domain::rrc); valid = security::verify_short_mac(short_mac, var_short_mac_input_packed, source_as_config); - logger.debug("Received RRC re-establishment. short_mac_valid={}.", valid); - + logger.debug("Received RRC re-establishment request. short_mac_valid={}", valid); } else { - logger.warning("Received RRC re-establishment, but old UE does not have valid security context."); + logger.warning("Received RRC re-establishment request, but old UE does not have valid security context"); } return valid; @@ -234,13 +265,7 @@ void rrc_reestablishment_procedure::send_rrc_reestablishment() rrc_reest.rrc_transaction_id = transaction.id(); rrc_reest.crit_exts.set_rrc_reest(); - // if the UE requests to reestablish RRC connection in the last serving gNB-DU, the DL RRC MESSAGE TRANSFER message - // shall include old gNB-DU UE F1AP ID, see TS 38.401 section 8.7 - if (get_du_index_from_ue_index(reestablishment_context.ue_index) == get_du_index_from_ue_index(context.ue_index)) { - rrc_ue_reest_notifier.on_new_dl_dcch(srb_id_t::srb1, dl_dcch_msg, reestablishment_context.ue_index); - } else { - rrc_ue_reest_notifier.on_new_dl_dcch(srb_id_t::srb1, dl_dcch_msg); - } + rrc_ue_reest_notifier.on_new_dl_dcch(srb_id_t::srb1, dl_dcch_msg); } void rrc_reestablishment_procedure::send_ue_context_release_request(ue_index_t ue_index) diff --git a/lib/rrc/ue/procedures/rrc_reestablishment_procedure.h b/lib/rrc/ue/procedures/rrc_reestablishment_procedure.h index 9fb4af639e..e6f9d02ce0 100644 --- a/lib/rrc/ue/procedures/rrc_reestablishment_procedure.h +++ b/lib/rrc/ue/procedures/rrc_reestablishment_procedure.h @@ -56,6 +56,9 @@ class rrc_reestablishment_procedure /// \brief Get and verify the reestablishment context of the reestablishing UE. bool get_and_verify_reestablishment_context(); + /// \brief Determined whether the Reestablishment Request is accepted or rejected. + bool is_reestablishment_rejected(); + /// \brief Get and verify the ShortMAC-I and update the keys. bool verify_security_context(); @@ -71,6 +74,8 @@ class rrc_reestablishment_procedure /// \brief Send UE Context Release Request. void send_ue_context_release_request(ue_index_t ue_index); + async_task handle_rrc_reestablishment_fallback(); + const asn1::rrc_nr::rrc_reest_request_s& reestablishment_request; rrc_ue_context_t& context; const byte_buffer& du_to_cu_container; @@ -89,6 +94,7 @@ class rrc_reestablishment_procedure rrc_transaction transaction; eager_async_task task; rrc_reestablishment_ue_context_t reestablishment_context; + bool context_transfer_success = false; bool context_modification_success = false; cu_cp_ue_context_release_request ue_context_release_request; }; diff --git a/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.cpp b/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.cpp index 7f6642ccd8..0536bf0361 100644 --- a/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.cpp +++ b/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.cpp @@ -49,7 +49,6 @@ void rrc_security_mode_command_procedure::operator()(coro_context>& ctx) context.state = rrc_state::connected; send_initial_ue_msg(transaction.response().msg.c1().rrc_setup_complete()); } else { - logger.debug("ue={} \"{}\" timed out after {}ms", context.ue_index, name(), context.cfg.rrc_procedure_timeout_ms); - rrc_ue.on_ue_delete_request(cause_protocol_t::unspecified); + logger.warning("ue={} \"{}\" timed out after {}ms", context.ue_index, name(), context.cfg.rrc_procedure_timeout_ms); + rrc_ue.on_ue_release_required(cause_protocol_t::unspecified); } CORO_RETURN(); @@ -96,11 +96,13 @@ void rrc_setup_procedure::send_rrc_setup() void rrc_setup_procedure::send_initial_ue_msg(const asn1::rrc_nr::rrc_setup_complete_s& rrc_setup_complete_msg) { - initial_ue_message init_ue_msg = {}; - init_ue_msg.ue_index = context.ue_index; - init_ue_msg.nas_pdu = rrc_setup_complete_msg.crit_exts.rrc_setup_complete().ded_nas_msg.copy(); - init_ue_msg.establishment_cause = cause; - init_ue_msg.cell = context.cell; + cu_cp_initial_ue_message init_ue_msg = {}; + init_ue_msg.ue_index = context.ue_index; + init_ue_msg.nas_pdu = rrc_setup_complete_msg.crit_exts.rrc_setup_complete().ded_nas_msg.copy(); + init_ue_msg.establishment_cause = static_cast(cause.value); + init_ue_msg.user_location_info.nr_cgi = context.cell.cgi; + init_ue_msg.user_location_info.tai.plmn_id = context.cell.cgi.plmn_hex; + init_ue_msg.user_location_info.tai.tac = context.cell.tac; cu_cp_five_g_s_tmsi five_g_s_tmsi; if (context.five_g_tmsi.has_value()) { diff --git a/lib/rrc/ue/procedures/rrc_ue_capability_transfer_procedure.cpp b/lib/rrc/ue/procedures/rrc_ue_capability_transfer_procedure.cpp index a8bd92670a..5b968a01dd 100644 --- a/lib/rrc/ue/procedures/rrc_ue_capability_transfer_procedure.cpp +++ b/lib/rrc/ue/procedures/rrc_ue_capability_transfer_procedure.cpp @@ -85,7 +85,7 @@ void rrc_ue_capability_transfer_procedure::operator()(coro_context>& ctx) mutable { CORO_BEGIN(ctx); diff --git a/lib/rrc/ue/rrc_ue_impl.h b/lib/rrc/ue/rrc_ue_impl.h index 1d430c4f1e..1d4bcc3942 100644 --- a/lib/rrc/ue/rrc_ue_impl.h +++ b/lib/rrc/ue/rrc_ue_impl.h @@ -77,7 +77,7 @@ class rrc_ue_impl final : public rrc_ue_interface static_vector get_srbs() override; // rrc_dl_nas_message_handler - void handle_dl_nas_transport_message(const dl_nas_transport_message& msg) override; + void handle_dl_nas_transport_message(byte_buffer nas_pdu) override; // rrc_ue_control_message_handler async_task handle_rrc_reconfiguration_request(const rrc_reconfiguration_procedure_request& msg) override; @@ -112,23 +112,17 @@ class rrc_ue_impl final : public rrc_ue_interface /// Packs a DL-CCCH message and logs the message void send_dl_ccch(const asn1::rrc_nr::dl_ccch_msg_s& dl_ccch_msg); - void send_dl_dcch(srb_id_t srb_id, - const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg, - ue_index_t old_ue_index = ue_index_t::invalid); + void send_dl_dcch(srb_id_t srb_id, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg); // rrc_ue_setup_proc_notifier void on_new_dl_ccch(const asn1::rrc_nr::dl_ccch_msg_s& dl_ccch_msg) override; - void on_ue_delete_request(const cause_t& cause) override; + void on_ue_release_required(const cause_t& cause) override; // rrc_ue_security_mode_command_proc_notifier void on_new_dl_dcch(srb_id_t srb_id, const asn1::rrc_nr::dl_dcch_msg_s& dl_ccch_msg) override; void on_new_as_security_context() override; bool get_security_enabled() override { return context.security_enabled; } - // rrc_ue_reestablishment_proc_notifier - void - on_new_dl_dcch(srb_id_t srb_id, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg, ue_index_t old_ue_index) override; - // initializes the security context and triggers the SMC procedure async_task handle_init_security_context(const security::security_context& sec_ctx) override; diff --git a/lib/rrc/ue/rrc_ue_message_handlers.cpp b/lib/rrc/ue/rrc_ue_message_handlers.cpp index 75e6d07d71..71391317f2 100644 --- a/lib/rrc/ue/rrc_ue_message_handlers.cpp +++ b/lib/rrc/ue/rrc_ue_message_handlers.cpp @@ -74,7 +74,7 @@ void rrc_ue_impl::handle_rrc_setup_request(const asn1::rrc_nr::rrc_setup_request if (reject_users) { logger.error("RRC connections not allowed. Sending Connection Reject"); send_rrc_reject(rrc_reject_max_wait_time_s); - on_ue_delete_request(cause_radio_network_t::unspecified); + on_ue_release_required(cause_radio_network_t::unspecified); return; } @@ -98,7 +98,7 @@ void rrc_ue_impl::handle_rrc_setup_request(const asn1::rrc_nr::rrc_setup_request default: logger.error("Unsupported RRCSetupRequest"); send_rrc_reject(rrc_reject_max_wait_time_s); - on_ue_delete_request(cause_protocol_t::unspecified); + on_ue_release_required(cause_protocol_t::unspecified); return; } context.connection_cause.value = request_ies.establishment_cause.value; @@ -204,6 +204,12 @@ void rrc_ue_impl::handle_ul_dcch_pdu(const srb_id_t srb_id, byte_buffer pdcp_pdu // Unpack PDCP PDU byte_buffer rrc_pdu = context.srbs.at(srb_id).unpack_pdcp_pdu(std::move(pdcp_pdu)); + if (rrc_pdu.empty()) { + logger.warning("ue={} C-RNTI={} RX {} Dropping PDU.", context.ue_index, context.c_rnti, srb_id); + logger.debug("original len={}, new_len={}", pdcp_pdu.length(), rrc_pdu.length()); + return; + } + logger.debug(rrc_pdu.begin(), rrc_pdu.end(), "ue={} C-RNTI={} RX {} RRC PDU ({} B)", @@ -216,10 +222,12 @@ void rrc_ue_impl::handle_ul_dcch_pdu(const srb_id_t srb_id, byte_buffer pdcp_pdu void rrc_ue_impl::handle_ul_info_transfer(const ul_info_transfer_ies_s& ul_info_transfer) { - ul_nas_transport_message ul_nas_msg = {}; - ul_nas_msg.ue_index = context.ue_index; - ul_nas_msg.cell = context.cell; - ul_nas_msg.nas_pdu = ul_info_transfer.ded_nas_msg.copy(); + cu_cp_ul_nas_transport ul_nas_msg = {}; + ul_nas_msg.ue_index = context.ue_index; + ul_nas_msg.nas_pdu = ul_info_transfer.ded_nas_msg.copy(); + ul_nas_msg.user_location_info.nr_cgi = context.cell.cgi; + ul_nas_msg.user_location_info.tai.plmn_id = context.cell.cgi.plmn_hex; + ul_nas_msg.user_location_info.tai.tac = context.cell.tac; nas_notifier.on_ul_nas_transport_message(ul_nas_msg); } @@ -232,14 +240,14 @@ void rrc_ue_impl::handle_measurement_report(const asn1::rrc_nr::meas_report_s& m cell_meas_mng.report_measurement(context.ue_index, meas_results); } -void rrc_ue_impl::handle_dl_nas_transport_message(const dl_nas_transport_message& msg) +void rrc_ue_impl::handle_dl_nas_transport_message(byte_buffer nas_pdu) { - logger.debug("Received DlNasTransportMessage ({} B)", msg.nas_pdu.length()); + logger.debug("Received DlNasTransportMessage ({} B)", nas_pdu.length()); dl_dcch_msg_s dl_dcch_msg; dl_info_transfer_ies_s& dl_info_transfer = dl_dcch_msg.msg.set_c1().set_dl_info_transfer().crit_exts.set_dl_info_transfer(); - dl_info_transfer.ded_nas_msg = msg.nas_pdu.copy(); + dl_info_transfer.ded_nas_msg = nas_pdu.copy(); if (context.srbs.find(srb_id_t::srb2) != context.srbs.end()) { send_dl_dcch(srb_id_t::srb2, dl_dcch_msg); @@ -254,7 +262,7 @@ void rrc_ue_impl::handle_rrc_transaction_complete(const ul_dcch_msg_s& msg, uint // Set transaction result and resume suspended procedure. if (not event_mng->transactions.set_response(transaction_id.value(), msg)) { - logger.warning("Unexpected transaction id={}", transaction_id.value()); + logger.warning("ue={} Unexpected RRC transaction id={}", context.ue_index, transaction_id.value()); } } @@ -299,7 +307,6 @@ async_task rrc_ue_impl::handle_handover_reconfiguration_complete_expected( procedure_result = true; } else { logger.debug("ue={} Did not receive RRC Reconfiguration Complete after HO - timed out.", context.ue_index); - this->on_ue_delete_request(cause_protocol_t::unspecified); // delete UE context if reconfig fails } CORO_RETURN(procedure_result); diff --git a/lib/rrc/ue/rrc_ue_message_senders.cpp b/lib/rrc/ue/rrc_ue_message_senders.cpp index fd5fe0d963..82dde34f91 100644 --- a/lib/rrc/ue/rrc_ue_message_senders.cpp +++ b/lib/rrc/ue/rrc_ue_message_senders.cpp @@ -43,7 +43,7 @@ void rrc_ue_impl::send_dl_ccch(const dl_ccch_msg_s& dl_ccch_msg) f1ap_pdu_notifier.on_new_rrc_pdu(srb_id_t::srb0, std::move(pdu)); } -void rrc_ue_impl::send_dl_dcch(srb_id_t srb_id, const dl_dcch_msg_s& dl_dcch_msg, ue_index_t old_ue_index) +void rrc_ue_impl::send_dl_dcch(srb_id_t srb_id, const dl_dcch_msg_s& dl_dcch_msg) { if (context.srbs.find(srb_id) == context.srbs.end()) { logger.error( @@ -62,7 +62,7 @@ void rrc_ue_impl::send_dl_dcch(srb_id_t srb_id, const dl_dcch_msg_s& dl_dcch_msg // pack PDCP PDU and send down the stack byte_buffer pdcp_pdu = context.srbs.at(srb_id).pack_rrc_pdu(std::move(pdu)); logger.debug(pdcp_pdu.begin(), pdcp_pdu.end(), "ue={} C-RNTI={} TX {} PDU", context.ue_index, context.c_rnti, srb_id); - f1ap_pdu_notifier.on_new_rrc_pdu(srb_id, std::move(pdcp_pdu), old_ue_index); + f1ap_pdu_notifier.on_new_rrc_pdu(srb_id, std::move(pdcp_pdu)); } void rrc_ue_impl::send_rrc_reject(uint8_t reject_wait_time_secs) diff --git a/lib/ru/ofh/ru_ofh_factory.cpp b/lib/ru/ofh/ru_ofh_factory.cpp index b25b233a34..b425d56ec3 100644 --- a/lib/ru/ofh/ru_ofh_factory.cpp +++ b/lib/ru/ofh/ru_ofh_factory.cpp @@ -25,7 +25,7 @@ #include "ru_ofh_rx_symbol_handler_impl.h" #include "ru_ofh_timing_handler.h" #include "srsran/ofh/ofh_factories.h" -#include "srsran/ofh/ofh_receiver.h" +#include "srsran/ofh/receiver/ofh_receiver.h" #include "srsran/ofh/transmitter/ofh_transmitter.h" #include "srsran/support/error_handling.h" diff --git a/lib/scheduler/cell/resource_grid.cpp b/lib/scheduler/cell/resource_grid.cpp index 4ce658e581..b34285bbc6 100644 --- a/lib/scheduler/cell/resource_grid.cpp +++ b/lib/scheduler/cell/resource_grid.cpp @@ -55,6 +55,20 @@ void carrier_subslot_resource_grid::fill(ofdm_symbol_range symbols, crb_interval } } +void carrier_subslot_resource_grid::fill(ofdm_symbol_range symbols, span crb_list) +{ + srsran_sanity_check(symbols.stop() <= NOF_OFDM_SYM_PER_SLOT_NORMAL_CP, "OFDM symbols out-of-bounds"); + + // carrier bitmap RB bit=0 corresponds to CRB=carrier offset. Thus, we need to shift the CRB interval. + for (unsigned i = symbols.start(); i < symbols.stop(); ++i) { + for (uint16_t crb : crb_list) { + srsran_sanity_check(rb_dims().contains(crb), "CRB interval out-of-bounds"); + crb -= offset(); + slot_rbs.set(crb + i * nof_rbs()); + } + } +} + bool carrier_subslot_resource_grid::collides(ofdm_symbol_range symbols, crb_interval crbs) const { srsran_sanity_check(rb_dims().contains(crbs), "CRB interval out-of-bounds"); @@ -70,6 +84,24 @@ bool carrier_subslot_resource_grid::collides(ofdm_symbol_range symbols, crb_inte return false; } +bool carrier_subslot_resource_grid::collides(ofdm_symbol_range symbols, span crb_list) const +{ + srsran_sanity_check(symbols.stop() <= NOF_OFDM_SYM_PER_SLOT_NORMAL_CP, "OFDM symbols out-of-bounds"); + + // carrier bitmap RB bit=0 corresponds to CRB=carrier offset. Thus, we need to shift the CRB interval. + for (unsigned i = symbols.start(); i < symbols.stop(); ++i) { + for (uint16_t crb : crb_list) { + srsran_sanity_check(rb_dims().contains(crb), "CRB interval out-of-bounds"); + crb -= offset(); + if (slot_rbs.test(crb + i * nof_rbs())) { + return true; + } + } + } + + return false; +} + crb_bitmap carrier_subslot_resource_grid::used_crbs(crb_interval bwp_crb_lims, ofdm_symbol_range symbols) const { srsran_sanity_check(symbols.stop() <= NOF_OFDM_SYM_PER_SLOT_NORMAL_CP, "OFDM symbols out-of-bounds"); @@ -139,6 +171,12 @@ void cell_slot_resource_grid::fill(grant_info grant) carrier.subslot_rbs.fill(grant.symbols, grant.crbs); } +void cell_slot_resource_grid::fill(subcarrier_spacing scs, ofdm_symbol_range ofdm_symbols, span crbs) +{ + auto& carrier = get_carrier(scs); + carrier.subslot_rbs.fill(ofdm_symbols, crbs); +} + bool cell_slot_resource_grid::collides(grant_info grant) const { const carrier_resource_grid& carrier = get_carrier(grant.scs); @@ -151,6 +189,14 @@ bool cell_slot_resource_grid::collides(subcarrier_spacing scs, ofdm_symbol_range return carrier.subslot_rbs.collides(ofdm_symbols, crbs); } +bool cell_slot_resource_grid::collides(subcarrier_spacing scs, + ofdm_symbol_range ofdm_symbols, + span crbs) const +{ + const carrier_resource_grid& carrier = get_carrier(scs); + return carrier.subslot_rbs.collides(ofdm_symbols, crbs); +} + crb_bitmap cell_slot_resource_grid::used_crbs(const bwp_configuration& bwp_cfg, ofdm_symbol_range symbols) const { const carrier_resource_grid& carrier = get_carrier(bwp_cfg.scs); @@ -209,7 +255,17 @@ cell_slot_resource_allocator::cell_slot_resource_allocator(const cell_configurat void cell_slot_resource_allocator::slot_indication(slot_point new_slot) { // Clear previous results. - result = {}; + result.dl.dl_pdcchs.clear(); + result.dl.ul_pdcchs.clear(); + result.dl.bc.ssb_info.clear(); + result.dl.bc.sibs.clear(); + result.dl.rar_grants.clear(); + result.dl.paging_grants.clear(); + result.dl.ue_grants.clear(); + result.dl.csi_rs.clear(); + result.ul.puschs.clear(); + result.ul.prachs.clear(); + result.ul.pucchs.clear(); dl_res_grid.clear(); ul_res_grid.clear(); diff --git a/lib/scheduler/cell/resource_grid.h b/lib/scheduler/cell/resource_grid.h index 8ae4e8a83e..b2a38ac224 100644 --- a/lib/scheduler/cell/resource_grid.h +++ b/lib/scheduler/cell/resource_grid.h @@ -94,12 +94,23 @@ class carrier_subslot_resource_grid /// \param crbs CRB interval, where CRB=0 corresponds to the CRB closest to pointA. void fill(ofdm_symbol_range symbols, crb_interval crbs); + /// Allocates the symbol x CRB list in the carrier resource grid. + /// \param symbols OFDM symbol interval of the allocation. Interval must fall within [0, 14). + /// \param crbs List of CRBs, where CRB=0 corresponds to the CRB closest to pointA. + void fill(ofdm_symbol_range symbols, span crb_list); + /// Checks whether the provided symbol x CRB range collides with any other allocation in the carrier resource grid. /// \param symbols OFDM symbol interval of the allocation. Interval must fall within [0, 14). /// \param crbs CRB interval, where CRB=0 corresponds to the CRB closest to pointA. /// \return true if a collision was detected. False otherwise. bool collides(ofdm_symbol_range symbols, crb_interval crbs) const; + /// Checks whether the provided symbol x CRB list collides with any other allocation in the carrier resource grid. + /// \param symbols OFDM symbol interval of the allocation. Interval must fall within [0, 14). + /// \param crbs List of CRBs, where CRB=0 corresponds to the CRB closest to pointA. + /// \return true if a collision was detected. False otherwise. + bool collides(ofdm_symbol_range symbols, span crb_list) const; + /// \brief Calculates a bitmap where each bit set one represents a CRB that is occupied or unavailable. /// A CRB is considered occupied if it is outside of the provided BWP CRB boundaries or if it is already allocated /// in at least one OFDM symbol of the provided OFDM symbol range. @@ -145,12 +156,14 @@ class cell_slot_resource_grid /// \param prbs PRB interval of the allocation. PRB=0 corresponds to the first PRB of the BWP. /// \param symbols OFDM symbol interval of the allocation. void fill(grant_info grant); + void fill(subcarrier_spacing scs, ofdm_symbol_range ofdm_symbols, span crbs); /// Checks whether the provided symbol x RB range collides with any other allocation in the cell resource grid. /// \param grant contains the symbol x RB range whose available we want to check. /// \return true if at least one symbol x RB of grant is currently occupied in the resource grid. bool collides(grant_info grant) const; bool collides(subcarrier_spacing scs, ofdm_symbol_range ofdm_symbols, crb_interval crbs) const; + bool collides(subcarrier_spacing scs, ofdm_symbol_range ofdm_symbols, span crbs) const; /// \brief Calculates a bitmap where each bit set to one represents a CRB that is occupied or unavailable. /// A CRB is considered occupied if it is outside of the provided BWP CRB boundaries or if it is already allocated diff --git a/lib/scheduler/common_scheduling/ra_scheduler.cpp b/lib/scheduler/common_scheduling/ra_scheduler.cpp index 90b8d7d9cb..ee000bf269 100644 --- a/lib/scheduler/common_scheduling/ra_scheduler.cpp +++ b/lib/scheduler/common_scheduling/ra_scheduler.cpp @@ -22,10 +22,10 @@ #include "ra_scheduler.h" #include "../logging/scheduler_event_logger.h" -#include "../pdcch_scheduling/pdcch_config_helpers.h" #include "../pdcch_scheduling/pdcch_resource_allocator_impl.h" #include "../support/dci_builder.h" #include "../support/dmrs_helpers.h" +#include "../support/pdcch/pdcch_mapping.h" #include "../support/pdsch/pdsch_default_time_allocation.h" #include "../support/pdsch/pdsch_resource_allocation.h" #include "../support/sch_pdu_builder.h" @@ -456,7 +456,7 @@ unsigned ra_scheduler::schedule_rar(const pending_rar_t& rar, cell_resource_allo return 0; } - // Ensure slot for RAR PDSCH has DL enabled. + // > Ensure slot for RAR PDSCH has DL enabled. if (not cell_cfg.is_dl_enabled(pdsch_alloc.slot)) { // Early exit. return 0; @@ -505,6 +505,7 @@ unsigned ra_scheduler::schedule_rar(const pending_rar_t& rar, cell_resource_allo auto pusch_list = get_pusch_time_domain_resource_table(get_pusch_cfg()); for (unsigned puschidx = 0; puschidx < pusch_list.size(); ++puschidx) { unsigned pusch_res_max_allocs = max_nof_allocs - msg3_candidates.size(); + // >> Verify if Msg3 delay provided by current PUSCH-TimeDomainResourceAllocation corresponds to an UL slot. const unsigned msg3_delay = get_msg3_delay(pusch_list[puschidx], get_ul_bwp_cfg().scs); const cell_slot_resource_allocator& msg3_alloc = res_alloc[msg3_delay]; @@ -541,6 +542,10 @@ unsigned ra_scheduler::schedule_rar(const pending_rar_t& rar, cell_resource_allo } } max_nof_allocs = msg3_candidates.size(); + if (max_nof_allocs == 0) { + log_postponed_rar(rar, "No PUSCH time domain resource found for Msg3."); + return 0; + } rar_crbs.resize(get_nof_pdsch_prbs_required(pdsch_time_res_index, max_nof_allocs).nof_prbs); // > Find space in PDCCH for RAR. @@ -752,7 +757,10 @@ void ra_scheduler::schedule_msg3_retx(cell_resource_allocator& res_alloc, pendin sch_prbs_tbs ra_scheduler::get_nof_pdsch_prbs_required(unsigned time_res_idx, unsigned nof_ul_grants) const { - return rar_data[time_res_idx].prbs_tbs_per_nof_grants[std::min(nof_ul_grants, (unsigned)MAX_RAR_PDUS_PER_SLOT) - 1]; + srsran_assert(nof_ul_grants > 0, "Invalid number of UL grants"); + + return rar_data[time_res_idx].prbs_tbs_per_nof_grants + [std::min(nof_ul_grants, (unsigned)rar_data[time_res_idx].prbs_tbs_per_nof_grants.size()) - 1]; } void ra_scheduler::log_postponed_rar(const pending_rar_t& rar, const char* cause_str) const diff --git a/lib/scheduler/common_scheduling/sib_scheduler.cpp b/lib/scheduler/common_scheduling/sib_scheduler.cpp index b49accd8b8..aa6196a03e 100644 --- a/lib/scheduler/common_scheduling/sib_scheduler.cpp +++ b/lib/scheduler/common_scheduling/sib_scheduler.cpp @@ -31,7 +31,7 @@ #include "../support/ssb_helpers.h" #include "srsran/ran/band_helper.h" #include "srsran/ran/pdcch/pdcch_type0_css_occasions.h" -#include "srsran/ran/sib_configuration.h" +#include "srsran/ran/sib/sib_configuration.h" using namespace srsran; diff --git a/lib/scheduler/config/serving_cell_config_factory.cpp b/lib/scheduler/config/serving_cell_config_factory.cpp index 384f03e0f9..48101ddb05 100644 --- a/lib/scheduler/config/serving_cell_config_factory.cpp +++ b/lib/scheduler/config/serving_cell_config_factory.cpp @@ -53,7 +53,7 @@ cell_config_builder_params_extended::cell_config_builder_params_extended(const c tdd_ul_dl_cfg_common->ref_scs = scs_common; tdd_ul_dl_cfg_common->pattern1.dl_ul_tx_period_nof_slots = 10; tdd_ul_dl_cfg_common->pattern1.nof_dl_slots = 6; - tdd_ul_dl_cfg_common->pattern1.nof_dl_symbols = 0; + tdd_ul_dl_cfg_common->pattern1.nof_dl_symbols = 8; tdd_ul_dl_cfg_common->pattern1.nof_ul_slots = 3; tdd_ul_dl_cfg_common->pattern1.nof_ul_symbols = 0; } else if (tdd_ul_dl_cfg_common.has_value() and band_helper::get_duplex_mode(band.value()) != duplex_mode::TDD) { @@ -923,3 +923,17 @@ srsran::config_helpers::make_pdsch_time_domain_resource(uint8_t return result; } + +ue_timers_and_constants_config srsran::config_helpers::make_default_ue_timers_and_constants_config() +{ + ue_timers_and_constants_config config; + config.t300 = std::chrono::milliseconds(1000); + config.t301 = std::chrono::milliseconds(1000); + config.t310 = std::chrono::milliseconds(1000); + config.n310 = 1; + config.t311 = std::chrono::milliseconds(30000); + config.n311 = 1; + config.t319 = std::chrono::milliseconds(1000); + + return config; +} diff --git a/lib/scheduler/logging/scheduler_result_logger.cpp b/lib/scheduler/logging/scheduler_result_logger.cpp index 01730eec6a..97f187d282 100644 --- a/lib/scheduler/logging/scheduler_result_logger.cpp +++ b/lib/scheduler/logging/scheduler_result_logger.cpp @@ -283,8 +283,17 @@ void scheduler_result_logger::log_debug(const sched_result& result) } if (fmtbuf.size() > 0) { - auto decision_latency = std::chrono::duration_cast(slot_stop_tp - slot_start_tp); - logger.debug("Slot decisions cell=0 t={}us:{}", decision_latency.count(), to_c_str(fmtbuf)); + auto decision_latency = std::chrono::duration_cast(slot_stop_tp - slot_start_tp); + const unsigned nof_pdschs = result.dl.paging_grants.size() + result.dl.rar_grants.size() + + result.dl.ue_grants.size() + result.dl.bc.sibs.size(); + const unsigned nof_puschs = result.ul.puschs.size(); + logger.debug("Slot decisions cell=0 t={}us ({} PDSCH{}, {} PUSCH{}):{}", + decision_latency.count(), + nof_pdschs, + nof_pdschs == 1 ? "" : "s", + nof_puschs, + nof_puschs == 1 ? "" : "s", + to_c_str(fmtbuf)); fmtbuf.clear(); } } @@ -359,8 +368,17 @@ void scheduler_result_logger::log_info(const sched_result& result) } if (fmtbuf.size() > 0) { - auto decision_latency = std::chrono::duration_cast(slot_stop_tp - slot_start_tp); - logger.info("Slot decisions cell=0 t={}us: {}", decision_latency.count(), to_c_str(fmtbuf)); + auto decision_latency = std::chrono::duration_cast(slot_stop_tp - slot_start_tp); + const unsigned nof_pdschs = result.dl.paging_grants.size() + result.dl.rar_grants.size() + + result.dl.ue_grants.size() + result.dl.bc.sibs.size(); + const unsigned nof_puschs = result.ul.puschs.size(); + logger.info("Slot decisions cell=0 t={}us ({} PDSCH{}, {} PUSCH{}): {}", + decision_latency.count(), + nof_pdschs, + nof_pdschs == 1 ? "" : "s", + nof_puschs, + nof_puschs == 1 ? "" : "s", + to_c_str(fmtbuf)); fmtbuf.clear(); } } diff --git a/lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.cpp b/lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.cpp index 87304228ae..54c541e058 100644 --- a/lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.cpp +++ b/lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.cpp @@ -21,8 +21,8 @@ */ #include "pdcch_resource_allocator_impl.h" +#include "../support/pdcch/pdcch_mapping.h" #include "../support/pdcch/pdcch_pdu_parameters.h" -#include "pdcch_config_helpers.h" #include "pdcch_slot_resource_allocator.h" #include "srsran/ran/pdcch/cce_to_prb_mapping.h" #include "srsran/ran/pdcch/pdcch_candidates.h" @@ -37,15 +37,27 @@ pdcch_resource_allocator_impl::pdcch_resource_allocator_impl(const cell_configur ? (*cell_cfg.dl_cfg_common.init_dl_bwp.pdcch_common.coreset0) : cell_cfg.dl_cfg_common.init_dl_bwp.pdcch_common.common_coreset.value(); for (unsigned lidx = 0; lidx != NOF_AGGREGATION_LEVELS; ++lidx) { - const aggregation_level aggr_lvl = aggregation_index_to_level(lidx); - const unsigned nof_candidates = ss.get_nof_candidates()[lidx]; - pdcch_common_candidates[ss.get_id()][lidx] = pdcch_candidates_common_ss_get_lowest_cce( + const aggregation_level aggr_lvl = aggregation_index_to_level(lidx); + const unsigned nof_candidates = ss.get_nof_candidates()[lidx]; + pdcch_candidate_info& aggr_lvl_candidates = pdcch_common_candidates[ss.get_id()][lidx]; + + aggr_lvl_candidates.candidates = pdcch_candidates_common_ss_get_lowest_cce( pdcch_candidates_common_ss_configuration{aggr_lvl, nof_candidates, cs_cfg.get_nof_cces()}); + aggr_lvl_candidates.candidate_crbs.resize(aggr_lvl_candidates.candidates.size()); + for (unsigned i = 0; i != aggr_lvl_candidates.candidates.size(); ++i) { + aggr_lvl_candidates.candidate_crbs[i] = pdcch_helper::cce_to_prb_mapping( + cell_cfg.dl_cfg_common.init_dl_bwp.generic_params, cs_cfg, cell_cfg.pci, aggr_lvl, i); + + // Convert PRBs to CRBs. + for (uint16_t& prb_idx : aggr_lvl_candidates.candidate_crbs[i]) { + prb_idx = crb_to_prb(cell_cfg.dl_cfg_common.init_dl_bwp.generic_params, prb_idx); + } + } } } for (unsigned i = 0; i < SLOT_ALLOCATOR_RING_SIZE; ++i) { - slot_records[i] = std::make_unique(cell_cfg); + slot_records[i] = std::make_unique(); } } @@ -84,7 +96,8 @@ pdcch_dl_information* pdcch_resource_allocator_impl::alloc_dl_pdcch_common(cell_ *cs_cfg, ss_cfg, aggr_lvl, - pdcch_common_candidates[ss_id][to_aggregation_level_index(aggr_lvl)]); + pdcch_common_candidates[ss_id][to_aggregation_level_index(aggr_lvl)].candidates, + pdcch_common_candidates[ss_id][to_aggregation_level_index(aggr_lvl)].candidate_crbs); } pdcch_ul_information* pdcch_resource_allocator_impl::alloc_ul_pdcch_common(cell_slot_resource_allocator& slot_alloc, @@ -106,7 +119,8 @@ pdcch_ul_information* pdcch_resource_allocator_impl::alloc_ul_pdcch_common(cell_ *cs_cfg, ss_cfg, aggr_lvl, - pdcch_common_candidates[ss_cfg.get_id()][to_aggregation_level_index(aggr_lvl)]); + pdcch_common_candidates[ss_id][to_aggregation_level_index(aggr_lvl)].candidates, + pdcch_common_candidates[ss_id][to_aggregation_level_index(aggr_lvl)].candidate_crbs); } pdcch_dl_information* pdcch_resource_allocator_impl::alloc_dl_pdcch_ue(cell_slot_resource_allocator& slot_alloc, @@ -116,11 +130,13 @@ pdcch_dl_information* pdcch_resource_allocator_impl::alloc_dl_pdcch_ue(cell_slot aggregation_level aggr_lvl) { // Find Common or UE-specific BWP and CORESET configurations. - const search_space_info& ss_cfg = user.search_space(ss_id); - const bwp_configuration& bwp_cfg = ss_cfg.bwp->dl_common->generic_params; - span candidates = ss_cfg.get_pdcch_candidates(aggr_lvl, slot_alloc.slot); + const search_space_info& ss_cfg = user.search_space(ss_id); + const bwp_configuration& bwp_cfg = ss_cfg.bwp->dl_common->generic_params; + span candidates = ss_cfg.get_pdcch_candidates(aggr_lvl, slot_alloc.slot); + span candidate_crbs = ss_cfg.get_crb_list_of_pdcch_candidates(aggr_lvl, slot_alloc.slot); - return alloc_dl_pdcch_helper(slot_alloc, rnti, bwp_cfg, *ss_cfg.coreset, *ss_cfg.cfg, aggr_lvl, candidates); + return alloc_dl_pdcch_helper( + slot_alloc, rnti, bwp_cfg, *ss_cfg.coreset, *ss_cfg.cfg, aggr_lvl, candidates, candidate_crbs); } pdcch_ul_information* pdcch_resource_allocator_impl::alloc_ul_pdcch_ue(cell_slot_resource_allocator& slot_alloc, @@ -130,11 +146,13 @@ pdcch_ul_information* pdcch_resource_allocator_impl::alloc_ul_pdcch_ue(cell_slot aggregation_level aggr_lvl) { // Find Common or UE-specific BWP and CORESET configurations. - const search_space_info& ss_cfg = user.search_space(ss_id); - const bwp_configuration& bwp_cfg = ss_cfg.bwp->ul_common->generic_params; - span candidates = ss_cfg.get_pdcch_candidates(aggr_lvl, slot_alloc.slot); + const search_space_info& ss_cfg = user.search_space(ss_id); + const bwp_configuration& bwp_cfg = ss_cfg.bwp->ul_common->generic_params; + span candidates = ss_cfg.get_pdcch_candidates(aggr_lvl, slot_alloc.slot); + span candidate_crbs = ss_cfg.get_crb_list_of_pdcch_candidates(aggr_lvl, slot_alloc.slot); - return alloc_ul_pdcch_helper(slot_alloc, rnti, bwp_cfg, *ss_cfg.coreset, *ss_cfg.cfg, aggr_lvl, candidates); + return alloc_ul_pdcch_helper( + slot_alloc, rnti, bwp_cfg, *ss_cfg.coreset, *ss_cfg.cfg, aggr_lvl, candidates, candidate_crbs); } pdcch_ul_information* pdcch_resource_allocator_impl::alloc_ul_pdcch_helper(cell_slot_resource_allocator& slot_alloc, @@ -143,7 +161,8 @@ pdcch_ul_information* pdcch_resource_allocator_impl::alloc_ul_pdcch_helper(cell_ const coreset_configuration& cs_cfg, const search_space_configuration& ss_cfg, aggregation_level aggr_lvl, - span candidates) + span candidates, + span candidate_crbs) { if (not pdcch_helper::is_pdcch_monitoring_active(slot_alloc.slot, ss_cfg)) { // PDCCH monitoring is not active in this slot. @@ -179,7 +198,7 @@ pdcch_ul_information* pdcch_resource_allocator_impl::alloc_ul_pdcch_helper(cell_ // Allocate a position for UL PDCCH in CORESET. pdcch_slot_allocator& pdcch_alloc = get_pdcch_slot_alloc(slot_alloc.slot); - if (not pdcch_alloc.alloc_pdcch(pdcch.ctx, slot_alloc, ss_cfg, candidates)) { + if (not pdcch_alloc.alloc_pdcch(pdcch.ctx, slot_alloc, ss_cfg, candidates, candidate_crbs)) { slot_alloc.result.dl.ul_pdcchs.pop_back(); return nullptr; } @@ -192,7 +211,8 @@ pdcch_dl_information* pdcch_resource_allocator_impl::alloc_dl_pdcch_helper(cell_ const coreset_configuration& cs_cfg, const search_space_configuration& ss_cfg, aggregation_level aggr_lvl, - span candidates) + span candidates, + span candidate_crbs) { if (not pdcch_helper::is_pdcch_monitoring_active(slot_alloc.slot, ss_cfg)) { // PDCCH monitoring is not active in this slot. @@ -228,7 +248,7 @@ pdcch_dl_information* pdcch_resource_allocator_impl::alloc_dl_pdcch_helper(cell_ // Allocate a position for DL PDCCH in CORESET. pdcch_slot_allocator& pdcch_alloc = get_pdcch_slot_alloc(slot_alloc.slot); - if (not pdcch_alloc.alloc_pdcch(pdcch.ctx, slot_alloc, ss_cfg, candidates)) { + if (not pdcch_alloc.alloc_pdcch(pdcch.ctx, slot_alloc, ss_cfg, candidates, candidate_crbs)) { slot_alloc.result.dl.dl_pdcchs.pop_back(); return nullptr; } diff --git a/lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.h b/lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.h index 08ab1e4adb..e150e7746a 100644 --- a/lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.h +++ b/lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.h @@ -31,6 +31,9 @@ namespace srsran { struct cell_slot_resource_allocator; class pdcch_slot_allocator; +/// List of CRBs for a given PDCCH candidate. +using crb_index_list = static_vector; + class pdcch_resource_allocator_impl final : public pdcch_resource_allocator { public: @@ -68,6 +71,11 @@ class pdcch_resource_allocator_impl final : public pdcch_resource_allocator /// allocated. static const size_t SLOT_ALLOCATOR_RING_SIZE = get_allocator_ring_size_gt_min(SCHEDULER_MAX_K0); + struct pdcch_candidate_info { + pdcch_candidate_list candidates; + static_vector candidate_crbs; + }; + pdcch_slot_allocator& get_pdcch_slot_alloc(slot_point sl); pdcch_dl_information* alloc_dl_pdcch_helper(cell_slot_resource_allocator& slot_alloc, @@ -76,7 +84,8 @@ class pdcch_resource_allocator_impl final : public pdcch_resource_allocator const coreset_configuration& cs_cfg, const search_space_configuration& ss_cfg, aggregation_level aggr_lvl, - span candidates); + span candidates, + span candidate_crbs); pdcch_ul_information* alloc_ul_pdcch_helper(cell_slot_resource_allocator& slot_alloc, rnti_t rnti, @@ -84,11 +93,12 @@ class pdcch_resource_allocator_impl final : public pdcch_resource_allocator const coreset_configuration& cs_cfg, const search_space_configuration& ss_cfg, aggregation_level aggr_lvl, - span candidates); + span candidates, + span candidate_crbs); const cell_configuration& cell_cfg; - slotted_id_vector> pdcch_common_candidates; + slotted_id_vector> pdcch_common_candidates; /// Last slot for which slot_indication has been called. slot_point last_sl_ind; diff --git a/lib/scheduler/pdcch_scheduling/pdcch_slot_resource_allocator.cpp b/lib/scheduler/pdcch_scheduling/pdcch_slot_resource_allocator.cpp index fe0b5d58ac..b54f0fd69d 100644 --- a/lib/scheduler/pdcch_scheduling/pdcch_slot_resource_allocator.cpp +++ b/lib/scheduler/pdcch_scheduling/pdcch_slot_resource_allocator.cpp @@ -22,7 +22,7 @@ #include "pdcch_slot_resource_allocator.h" #include "../cell/resource_grid.h" -#include "pdcch_config_helpers.h" +#include "../support/pdcch/pdcch_mapping.h" #include "srsran/ran/pdcch/cce_to_prb_mapping.h" using namespace srsran; @@ -39,16 +39,18 @@ void pdcch_slot_allocator::clear() bool pdcch_slot_allocator::alloc_pdcch(dci_context_information& pdcch_ctx, cell_slot_resource_allocator& slot_alloc, const search_space_configuration& ss_cfg, - span ss_candidates) + span ss_candidates, + span ss_candidate_crbs) { saved_dfs_tree.clear(); // Create an DL Allocation Record. alloc_record record{}; - record.is_dl = true; - record.pdcch_ctx = &pdcch_ctx; - record.ss_cfg = &ss_cfg; - record.pdcch_candidates = ss_candidates; + record.is_dl = true; + record.pdcch_ctx = &pdcch_ctx; + record.ss_cfg = &ss_cfg; + record.pdcch_candidates = ss_candidates; + record.pdcch_candidate_crbs = ss_candidate_crbs; // Try to allocate PDCCH for one of the possible CCE positions. If this operation fails, retry it, but using a // different permutation of past grant CCE positions. @@ -119,7 +121,7 @@ bool pdcch_slot_allocator::alloc_dfs_node(cell_slot_resource_allocator& slot_all node.ncce = cce_locs[node.dci_iter_index]; // Attempt to allocate PDCCH with provided CCE position. - if (not allocate_cce(slot_alloc, node.ncce, record)) { + if (not allocate_cce(slot_alloc, record, node.dci_iter_index)) { continue; } @@ -150,36 +152,24 @@ bool pdcch_slot_allocator::get_next_dfs(cell_slot_resource_allocator& slot_alloc } bool pdcch_slot_allocator::allocate_cce(cell_slot_resource_allocator& slot_alloc, - unsigned ncce, - const alloc_record& record) + const alloc_record& record, + unsigned dci_iter_index) { - const bwp_configuration& bwp_cfg = *record.pdcch_ctx->bwp_cfg; - const coreset_configuration& cs_cfg = *record.pdcch_ctx->coreset_cfg; - prb_index_list pdcch_prbs = - pdcch_helper::cce_to_prb_mapping(bwp_cfg, cs_cfg, cell_cfg.pci, record.pdcch_ctx->cces.aggr_lvl, ncce); - grant_info grant; + const bwp_configuration& bwp_cfg = *record.pdcch_ctx->bwp_cfg; + const coreset_configuration& cs_cfg = *record.pdcch_ctx->coreset_cfg; + const crb_index_list& pdcch_crbs = record.pdcch_candidate_crbs[dci_iter_index]; + grant_info grant; grant.scs = bwp_cfg.scs; // Check the current CCE position collides with an existing one. - // TODO: Optimize. - for (uint16_t prb : pdcch_prbs) { - unsigned crb = prb_to_crb(*record.pdcch_ctx->bwp_cfg, prb); - grant.crbs = {crb, crb + 1}; - grant.symbols = {0, (uint8_t)cs_cfg.duration}; - if (slot_alloc.dl_res_grid.collides(grant)) { - // Collision detected. Try another CCE position. - return false; - } + ofdm_symbol_range symbols{0, (uint8_t)cs_cfg.duration}; + if (slot_alloc.dl_res_grid.collides(record.pdcch_ctx->bwp_cfg->scs, symbols, pdcch_crbs)) { + // Collision detected. Try another CCE position. + return false; } // Allocation successful. - // TODO: Optimize. - for (uint16_t prb : pdcch_prbs) { - unsigned crb = prb_to_crb(*record.pdcch_ctx->bwp_cfg, prb); - grant.crbs = {crb, crb + 1}; - grant.symbols = {0, (uint8_t)cs_cfg.duration}; - slot_alloc.dl_res_grid.fill(grant); - } + slot_alloc.dl_res_grid.fill(record.pdcch_ctx->bwp_cfg->scs, symbols, pdcch_crbs); return true; } diff --git a/lib/scheduler/pdcch_scheduling/pdcch_slot_resource_allocator.h b/lib/scheduler/pdcch_scheduling/pdcch_slot_resource_allocator.h index cf85bdbd25..ba83a20f1c 100644 --- a/lib/scheduler/pdcch_scheduling/pdcch_slot_resource_allocator.h +++ b/lib/scheduler/pdcch_scheduling/pdcch_slot_resource_allocator.h @@ -22,6 +22,7 @@ #pragma once +#include "../support/pdcch/pdcch_mapping.h" #include "srsran/ran/pdcch/pdcch_candidates.h" #include "srsran/scheduler/scheduler_dci.h" #include "srsran/scheduler/scheduler_slot_handler.h" @@ -31,6 +32,9 @@ namespace srsran { struct cell_slot_resource_allocator; class cell_configuration; +/// List of CRBs for a given PDCCH candidate. +using crb_index_list = static_vector; + class pdcch_slot_allocator { public: @@ -40,6 +44,7 @@ class pdcch_slot_allocator dci_context_information* pdcch_ctx; const search_space_configuration* ss_cfg; span pdcch_candidates; + span pdcch_candidate_crbs; }; /// DFS decision tree node. @@ -49,7 +54,6 @@ class pdcch_slot_allocator unsigned record_index; }; - explicit pdcch_slot_allocator(const cell_configuration& cell_cfg_) : cell_cfg(cell_cfg_) {} ~pdcch_slot_allocator(); /// Erase the current PDCCH allocations and stored context for this slot. @@ -60,7 +64,8 @@ class pdcch_slot_allocator bool alloc_pdcch(dci_context_information& pdcch_ctx, cell_slot_resource_allocator& slot_alloc, const search_space_configuration& ss_cfg, - span ss_candidates); + span ss_candidates, + span ss_candidate_crbs); /// Deallocates the last PDCCH CCE space reservation. bool cancel_last_pdcch(cell_slot_resource_allocator& slot_alloc); @@ -70,9 +75,7 @@ class pdcch_slot_allocator bool get_next_dfs(cell_slot_resource_allocator& slot_alloc); /// Allocate CCEs of a given PDCCH. - bool allocate_cce(cell_slot_resource_allocator& slot_alloc, unsigned ncce, const alloc_record& record); - - const cell_configuration& cell_cfg; + bool allocate_cce(cell_slot_resource_allocator& slot_alloc, const alloc_record& record, unsigned dci_iter_index); /// list of grants in a given slot. static_vector records; diff --git a/lib/scheduler/policy/scheduler_policy.h b/lib/scheduler/policy/scheduler_policy.h index 363a14d34f..73e0947702 100644 --- a/lib/scheduler/policy/scheduler_policy.h +++ b/lib/scheduler/policy/scheduler_policy.h @@ -42,6 +42,11 @@ class ue_resource_grid_view return cell_res_grids[cell_index]->cfg; } + const cell_slot_resource_grid& get_pdcch_grid(du_cell_index_t cell_index) const + { + return (*cell_res_grids[cell_index])[0].dl_res_grid; + } + const cell_slot_resource_grid& get_pdsch_grid(du_cell_index_t cell_index, unsigned k0) const { return (*cell_res_grids[cell_index])[k0].dl_res_grid; @@ -52,6 +57,11 @@ class ue_resource_grid_view return (*cell_res_grids[cell_index])[k2].ul_res_grid; } + span get_ue_pdsch_grants(du_cell_index_t cell_index, unsigned k0) const + { + return (*cell_res_grids[cell_index])[k0].result.dl.ue_grants; + } + bool has_ue_dl_pdcch(du_cell_index_t cell_index, rnti_t rnti) const { const auto& pdcchs = (*cell_res_grids[cell_index])[0].result.dl.dl_pdcchs; @@ -98,22 +108,16 @@ class scheduler_policy /// gNB resource grid. /// \param[in] res_grid view of the current resource grid occupancy state for all gnb cells. /// \param[in] ues List of eligible UEs to be scheduled in the given slot. - /// \param[in] is_retx Flag indicating DL grants for retransmissions or new transmissions. - virtual void dl_sched(ue_pdsch_allocator& pdsch_alloc, - const ue_resource_grid_view& res_grid, - const ue_repository& ues, - bool is_retx) = 0; + virtual void + dl_sched(ue_pdsch_allocator& pdsch_alloc, const ue_resource_grid_view& res_grid, const ue_repository& ues) = 0; /// Schedule UE UL grants for a given {slot, cell}. /// \param[out] pusch_alloc PUSCH grant allocator. This object provides a handle to allocate PUSCH grants in the /// gNB resource grid. /// \param[in] res_grid view of the current resource grid occupancy state for all gnb cells. /// \param[in] ues List of eligible UEs to be scheduled in the given slot. - /// \param[in] is_retx Flag indicating UL grants for retransmissions or new transmissions. - virtual void ul_sched(ue_pusch_allocator& pusch_alloc, - const ue_resource_grid_view& res_grid, - const ue_repository& ues, - bool is_retx) = 0; + virtual void + ul_sched(ue_pusch_allocator& pusch_alloc, const ue_resource_grid_view& res_grid, const ue_repository& ues) = 0; }; } // namespace srsran diff --git a/lib/scheduler/policy/scheduler_time_rr.cpp b/lib/scheduler/policy/scheduler_time_rr.cpp index ee7e578183..1e67facbf8 100644 --- a/lib/scheduler/policy/scheduler_time_rr.cpp +++ b/lib/scheduler/policy/scheduler_time_rr.cpp @@ -31,14 +31,9 @@ using namespace srsran; /// \param[in] ue_db map of "slot_ue" /// \param[in] next_ue_index UE index with the highest priority to be allocated. /// \param[in] alloc_ue callable with signature "bool(ue&)" that returns true if UE allocation was successful. -/// \param[in] stop_cond callable with signature "bool()" that verifies if the conditions are present for the -/// round-robin to early-stop the iteration. /// \return Index of next UE to allocate. -template -du_ue_index_t round_robin_apply(const ue_repository& ue_db, - du_ue_index_t next_ue_index, - const AllocUEFunc& alloc_ue, - const StopIterationFunc& stop_cond) +template +du_ue_index_t round_robin_apply(const ue_repository& ue_db, du_ue_index_t next_ue_index, const AllocUEFunc& alloc_ue) { if (ue_db.empty()) { return next_ue_index; @@ -50,29 +45,34 @@ du_ue_index_t round_robin_apply(const ue_repository& ue_db, // wrap-around it = ue_db.begin(); } - const ue& u = **it; - if (alloc_ue(u)) { - if (first_alloc) { - next_ue_index = to_du_ue_index((unsigned)u.ue_index + 1U); - first_alloc = false; - } - if (stop_cond()) { - break; - } + const ue& u = **it; + const alloc_outcome alloc_result = alloc_ue(u); + if (alloc_result == alloc_outcome::skip_slot) { + // Grid allocator directed policy to stop allocations for this slot. + break; + } + + if (alloc_result == alloc_outcome::success and first_alloc) { + // Mark the next UE to be the first UE to get allocated in the following slot. + // It is important that we equally distribute the opportunity to be the first UE being allocated in a slot for + // all UEs. Otherwise, we could end up in a situation, where a UE is always the last one to be allocated and + // can only use the RBs that were left from the previous UE allocations. + next_ue_index = to_du_ue_index((unsigned)u.ue_index + 1U); + first_alloc = false; } } return next_ue_index; } /// Allocate UE PDSCH grant. -static bool alloc_dl_ue(const ue& u, - const ue_resource_grid_view& res_grid, - ue_pdsch_allocator& pdsch_alloc, - bool is_retx, - srslog::basic_logger& logger) +static alloc_outcome alloc_dl_ue(const ue& u, + const ue_resource_grid_view& res_grid, + ue_pdsch_allocator& pdsch_alloc, + bool is_retx, + srslog::basic_logger& logger) { if (not is_retx and not u.has_pending_dl_newtx_bytes()) { - return false; + return alloc_outcome::skip_ue; } const slot_point pdcch_slot = res_grid.get_pdcch_slot(); @@ -80,11 +80,17 @@ static bool alloc_dl_ue(const ue& u, for (unsigned i = 0; i != u.nof_cells(); ++i) { const ue_cell& ue_cc = u.get_cell(to_ue_cell_index(i)); + // UE is already allocated in the PDCCH for this slot (e.g. we should skip a newTx if a reTx has already been + // allocated for this UE). + if (res_grid.has_ue_dl_pdcch(ue_cc.cell_index, u.crnti)) { + return alloc_outcome::skip_ue; + } + ue_pdsch_param_candidate_searcher candidates{u, to_ue_cell_index(i), is_retx, pdcch_slot}; if (candidates.dl_harqs().empty()) { if (not is_retx) { - if (res_grid.has_ue_dl_pdcch(ue_cc.cell_index, u.crnti) or ue_cc.harqs.find_dl_harq_waiting_ack() == nullptr) { + if (ue_cc.harqs.find_dl_harq_waiting_ack() == nullptr) { // A HARQ is already being retransmitted, or all HARQs are waiting for a grant for a retransmission. logger.debug("ue={} rnti={:#x} PDSCH allocation skipped. Cause: No available HARQs for new transmissions.", ue_cc.ue_index, @@ -116,7 +122,7 @@ static bool alloc_dl_ue(const ue& u, : ue_cc.required_dl_prbs(pdsch, u.pending_dl_newtx_bytes(), dci_type); if (mcs_prbs.n_prbs == 0) { logger.debug("ue={} rnti={:#x} PDSCH allocation skipped. Cause: UE's CQI=0 ", ue_cc.ue_index, ue_cc.rnti()); - return false; + return alloc_outcome::skip_ue; } // [Implementation-defined] In case of partial slots and nof. PRBs allocated equals to 1 probability of KO is @@ -136,42 +142,53 @@ static bool alloc_dl_ue(const ue& u, if (are_crbs_valid) { const aggregation_level aggr_lvl = ue_cc.get_aggregation_level(ue_cc.channel_state_manager().get_wideband_cqi(), ss, true); - const bool res_allocated = pdsch_alloc.allocate_dl_grant(ue_pdsch_grant{&u, - ue_cc.cell_index, - h.id, - ss.cfg->get_id(), - param_candidate.pdsch_td_res_index(), - ue_grant_crbs, - aggr_lvl, - mcs_prbs.mcs}); - if (res_allocated) { - return true; + const alloc_outcome result = pdsch_alloc.allocate_dl_grant(ue_pdsch_grant{&u, + ue_cc.cell_index, + h.id, + ss.cfg->get_id(), + param_candidate.pdsch_td_res_index(), + ue_grant_crbs, + aggr_lvl, + mcs_prbs.mcs}); + // If the allocation failed due to invalid parameters, we continue iteration. + if (result != alloc_outcome::invalid_params) { + return result; } } } } - return false; + return alloc_outcome::skip_ue; } /// Allocate UE PUSCH grant. -static bool alloc_ul_ue(const ue& u, - const ue_resource_grid_view& res_grid, - ue_pusch_allocator& pusch_alloc, - bool is_retx, - srslog::basic_logger& logger) +static alloc_outcome alloc_ul_ue(const ue& u, + const ue_resource_grid_view& res_grid, + ue_pusch_allocator& pusch_alloc, + bool is_retx, + bool schedule_sr_only, + srslog::basic_logger& logger) { unsigned pending_newtx_bytes = 0; if (not is_retx) { + if (schedule_sr_only and not u.has_pending_sr()) { + return alloc_outcome::skip_ue; + } pending_newtx_bytes = u.pending_ul_newtx_bytes(); if (pending_newtx_bytes == 0) { - return false; + return alloc_outcome::skip_ue; } } const slot_point pdcch_slot = res_grid.get_pdcch_slot(); // Prioritize PCell over SCells. for (unsigned i = 0; i != u.nof_cells(); ++i) { - const ue_cell& ue_cc = u.get_cell(to_ue_cell_index(i)); + const ue_cell& ue_cc = u.get_cell(to_ue_cell_index(i)); + + // UE is already allocated resources. + if (res_grid.has_ue_ul_pdcch(ue_cc.cell_index, u.crnti)) { + return alloc_outcome::skip_ue; + } + const cell_configuration& cell_cfg_common = res_grid.get_cell_cfg_common(ue_cc.cell_index); const ul_harq_process* h = is_retx ? ue_cc.harqs.find_pending_ul_retx() : ue_cc.harqs.find_empty_ul_harq(); if (h == nullptr) { @@ -246,7 +263,7 @@ static bool alloc_ul_ue(const ue& u, "allocated to this UE", ue_cc.ue_index, ue_cc.rnti()); - return false; + return alloc_outcome::skip_ue; } // Due to the pre-allocated UCI bits, MCS 0 and PRB 1 would not leave any space for the payload on the TBS, as @@ -257,13 +274,26 @@ static bool alloc_ul_ue(const ue& u, // TODO: Remove this part and handle the problem with a loop that is general for any configuration. const sch_mcs_index min_mcs_for_1_prb = static_cast(6U); const unsigned min_allocable_prbs = 1U; - if (mcs_prbs.mcs < min_mcs_for_1_prb and mcs_prbs.n_prbs == min_allocable_prbs) { + if (mcs_prbs.mcs < min_mcs_for_1_prb and mcs_prbs.n_prbs <= min_allocable_prbs) { ++mcs_prbs.n_prbs; } const crb_interval ue_grant_crbs = rb_helper::find_empty_interval_of_length(used_crbs, mcs_prbs.n_prbs, 0); // There must be at least one available CRB. bool are_crbs_valid = not ue_grant_crbs.empty(); + // For low MCS, we need to allocate more than min_allocable_prbs PRBs; else, the overhead due to UCI-on-PUSCH will + // make the effective code-rate exceed 0.95. + // TODO: Remove this part and handle the problem with a loop that is general for any configuration. + if (ue_grant_crbs.length() <= min_allocable_prbs and mcs_prbs.mcs < min_mcs_for_1_prb) { + logger.debug("ue={} rnti={:#x} PUSCH allocation skipped. Cause: the scheduler couldn't allocate the min. " + "number of PRBs={} for MCS={}", + ue_cc.ue_index, + ue_cc.rnti(), + mcs_prbs.n_prbs, + mcs_prbs.mcs.to_uint()); + return alloc_outcome::skip_ue; + } + if (is_retx) { // In case of Retx, the #CRBs need to stay the same. are_crbs_valid = ue_grant_crbs.length() == h->last_tx_params().rbs.type1().length(); @@ -271,22 +301,23 @@ static bool alloc_ul_ue(const ue& u, if (are_crbs_valid) { const aggregation_level aggr_lvl = ue_cc.get_aggregation_level(ue_cc.channel_state_manager().get_wideband_cqi(), *ss, false); - const bool res_allocated = pusch_alloc.allocate_ul_grant(ue_pusch_grant{&u, - ue_cc.cell_index, - h->id, - ue_grant_crbs, - pusch_td.symbols, - time_res, - ss->cfg->get_id(), - aggr_lvl, - mcs_prbs.mcs}); - if (res_allocated) { - return true; + const alloc_outcome result = pusch_alloc.allocate_ul_grant(ue_pusch_grant{&u, + ue_cc.cell_index, + h->id, + ue_grant_crbs, + pusch_td.symbols, + time_res, + ss->cfg->get_id(), + aggr_lvl, + mcs_prbs.mcs}); + // If the allocation failed due to invalid parameters, we continue the iteration. + if (result != alloc_outcome::invalid_params) { + return result; } } } } - return false; + return alloc_outcome::skip_ue; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -300,40 +331,38 @@ scheduler_time_rr::scheduler_time_rr() : void scheduler_time_rr::dl_sched(ue_pdsch_allocator& pdsch_alloc, const ue_resource_grid_view& res_grid, - const ue_repository& ues, - bool is_retx) + const ue_repository& ues) { - auto tx_ue_function = [this, &res_grid, &pdsch_alloc, is_retx](const ue& u) { - return alloc_dl_ue(u, res_grid, pdsch_alloc, is_retx, logger); + auto tx_ue_function = [this, &res_grid, &pdsch_alloc](const ue& u) { + return alloc_dl_ue(u, res_grid, pdsch_alloc, false, logger); }; - auto stop_iter = [&res_grid]() { - // TODO: Account for different BWPs and cells. - const du_cell_index_t cell_idx = to_du_cell_index(0); - const auto& init_dl_bwp = res_grid.get_cell_cfg_common(cell_idx).dl_cfg_common.init_dl_bwp; - // If all RBs are occupied, stop iteration. - return res_grid.get_pdsch_grid(cell_idx, init_dl_bwp.pdsch_common.pdsch_td_alloc_list[0].k0) - .used_crbs(init_dl_bwp.generic_params, init_dl_bwp.pdsch_common.pdsch_td_alloc_list[0].symbols) - .all(); + auto retx_ue_function = [this, &res_grid, &pdsch_alloc](const ue& u) { + return alloc_dl_ue(u, res_grid, pdsch_alloc, true, logger); }; - next_dl_ue_index = round_robin_apply(ues, next_dl_ue_index, tx_ue_function, stop_iter); + + // First schedule re-transmissions. + next_dl_ue_index = round_robin_apply(ues, next_dl_ue_index, retx_ue_function); + // Then, schedule new transmissions. + next_dl_ue_index = round_robin_apply(ues, next_dl_ue_index, tx_ue_function); } void scheduler_time_rr::ul_sched(ue_pusch_allocator& pusch_alloc, const ue_resource_grid_view& res_grid, - const ue_repository& ues, - bool is_retx) + const ue_repository& ues) { - auto tx_ue_function = [this, &res_grid, &pusch_alloc, is_retx](const ue& u) { - return alloc_ul_ue(u, res_grid, pusch_alloc, is_retx, logger); + auto data_retx_ue_function = [this, &res_grid, &pusch_alloc](const ue& u) { + return alloc_ul_ue(u, res_grid, pusch_alloc, true, false, logger); + }; + auto data_tx_ue_function = [this, &res_grid, &pusch_alloc](const ue& u) { + return alloc_ul_ue(u, res_grid, pusch_alloc, false, false, logger); }; - auto stop_iter = [&res_grid]() { - // TODO: Account for different BWPs and cells. - const du_cell_index_t cell_idx = to_du_cell_index(0); - auto& init_ul_bwp = res_grid.get_cell_cfg_common(cell_idx).ul_cfg_common.init_ul_bwp; - // If all RBs are occupied, stop iteration. - return res_grid.get_pusch_grid(cell_idx, init_ul_bwp.pusch_cfg_common->pusch_td_alloc_list[0].k2) - .used_crbs(init_ul_bwp.generic_params, init_ul_bwp.pusch_cfg_common->pusch_td_alloc_list[0].symbols) - .all(); + auto sr_ue_function = [this, &res_grid, &pusch_alloc](const ue& u) { + return alloc_ul_ue(u, res_grid, pusch_alloc, false, true, logger); }; - next_ul_ue_index = round_robin_apply(ues, next_ul_ue_index, tx_ue_function, stop_iter); + // First schedule UL data re-transmissions. + next_ul_ue_index = round_robin_apply(ues, next_ul_ue_index, data_retx_ue_function); + // Then, schedule all pending SR. + next_ul_ue_index = round_robin_apply(ues, next_ul_ue_index, sr_ue_function); + // Finally, schedule UL data new transmissions. + next_ul_ue_index = round_robin_apply(ues, next_ul_ue_index, data_tx_ue_function); } diff --git a/lib/scheduler/policy/scheduler_time_rr.h b/lib/scheduler/policy/scheduler_time_rr.h index a96f71e689..125bc651f6 100644 --- a/lib/scheduler/policy/scheduler_time_rr.h +++ b/lib/scheduler/policy/scheduler_time_rr.h @@ -31,15 +31,11 @@ class scheduler_time_rr : public scheduler_policy public: scheduler_time_rr(); - void dl_sched(ue_pdsch_allocator& pdsch_alloc, - const ue_resource_grid_view& res_grid, - const ue_repository& ues, - bool is_retx) override; - - void ul_sched(ue_pusch_allocator& pusch_alloc, - const ue_resource_grid_view& res_grid, - const ue_repository& ues, - bool is_retx) override; + void + dl_sched(ue_pdsch_allocator& pdsch_alloc, const ue_resource_grid_view& res_grid, const ue_repository& ues) override; + + void + ul_sched(ue_pusch_allocator& pusch_alloc, const ue_resource_grid_view& res_grid, const ue_repository& ues) override; private: srslog::basic_logger& logger; diff --git a/lib/scheduler/policy/ue_allocator.h b/lib/scheduler/policy/ue_allocator.h index 2c7063b95a..f040516df9 100644 --- a/lib/scheduler/policy/ue_allocator.h +++ b/lib/scheduler/policy/ue_allocator.h @@ -56,22 +56,31 @@ struct ue_pusch_grant { sch_mcs_index mcs; }; -/// Allocator of PDSCH grants for UE RLC data. +/// \brief Outcome of a UE grant allocation, and action for the scheduler policy to follow afterwards. +/// +/// The current outcomes are: +/// - success - the allocation was successful with the provided parameters. +/// - skip_slot - failure to allocate and the scheduler policy should terminate the current slot processing. +/// - skip_ue - failure to allocate and the scheduler policy should move on to the next candidate UE. +/// - invalid_params - failure to allocate and the scheduler policy should try a different set of grant parameters. +enum class alloc_outcome { success, skip_slot, skip_ue, invalid_params }; + +/// Allocator of PDSCH grants for UEs. class ue_pdsch_allocator { public: virtual ~ue_pdsch_allocator() = default; - virtual bool allocate_dl_grant(const ue_pdsch_grant& grant) = 0; + virtual alloc_outcome allocate_dl_grant(const ue_pdsch_grant& grant) = 0; }; -/// Allocator of PUSCH grants for UE RLC data. +/// Allocator of PUSCH grants for UEs. class ue_pusch_allocator { public: virtual ~ue_pusch_allocator() = default; - virtual bool allocate_ul_grant(const ue_pusch_grant& grant) = 0; + virtual alloc_outcome allocate_ul_grant(const ue_pusch_grant& grant) = 0; }; } // namespace srsran diff --git a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp index 2f65d61926..99f7a5d437 100644 --- a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp +++ b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp @@ -211,7 +211,10 @@ pucch_harq_ack_grant pucch_allocator_impl::alloc_common_pucch_harq_ack_ue(cell_r pucch_harq_ack_output.pucch_pdu = &pucch_info; pucch_harq_ack_output.pucch_res_indicator = pucch_res.value().pucch_res_indicator; - logger.debug("tc-rnti={:#x}: PUCCH HARQ-ACK common allocated for slot={}", tcrnti, pucch_slot_alloc.slot); + logger.debug("tc-rnti={:#x}: PUCCH HARQ-ACK common with res_ind={} allocated for slot={}", + tcrnti, + pucch_harq_ack_output.pucch_res_indicator, + pucch_slot_alloc.slot); return pucch_harq_ack_output; } @@ -443,9 +446,7 @@ void pucch_allocator_impl::pucch_allocate_csi_opportunity(cell_slot_resource_all srsran_assert(existing_grants.format2_csi_grant == nullptr, "No PUCCH F2 grants for CSI are expected at this point"); - // Case A) There are no existing PUCCH grants, allocate a new one for CSI. if (existing_grants.format1_sr_grant == nullptr) { - // Allocate new resource Format 2. return allocate_new_csi_grant(pucch_slot_alloc, crnti, ue_cell_cfg, csi_part1_nof_bits); } else { return convert_to_format2_csi(pucch_slot_alloc, @@ -763,6 +764,10 @@ pucch_allocator_impl::allocate_new_format1_harq_grant(cell_slot_resource_allocat pucch_harq_ack_output.pucch_pdu = &pucch_pdu; pucch_harq_ack_output.pucch_res_indicator = static_cast(pucch_harq_res_info.pucch_res_indicator); + logger.debug("rnti={:#x}: PUCCH HARQ-ACK allocation on F1 with res_ind={} for slot={} completed", + crnti, + pucch_harq_ack_output.pucch_res_indicator, + pucch_slot_alloc.slot); return pucch_harq_ack_output; } @@ -782,12 +787,26 @@ void pucch_allocator_impl::convert_to_format2_csi(cell_slot_resource_allocator& const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); // Get a PUCCH Format 2 resource. - // If there are no HARQ-ACK bits to be reported, then get a PUCCH format 2 CSI-specific resource. - const pucch_harq_resource_alloc_record format2_res = - curr_harq_bits != 0 ? resource_manager.reserve_next_f2_harq_res_available(pucch_slot_alloc.slot, rnti, pucch_cfg) - : pucch_harq_resource_alloc_record{.pucch_res = resource_manager.reserve_csi_resource( - pucch_slot_alloc.slot, rnti, ue_cell_cfg), - .pucch_res_indicator = 0}; + pucch_harq_resource_alloc_record format2_res{.pucch_res = nullptr, .pucch_res_indicator = 0}; + // If there are (previously allocate) HARQ-ACK bits to be reported, when we allocate a PUCCH F2 resource with CSI + // bits, we need to keep the previous PUCCH resource indicator (this was sent previously in a DCI to the UE). Without + // HARQ-ACK bits, we allocate the CSI-specific PUCCH resource, which doesn't use the PUCCH resource indicator. + if (curr_harq_bits != 0 and not is_fallback_mode) { + const int pucch_res = resource_manager.fetch_f1_pucch_res_indic(pucch_slot_alloc.slot, rnti, pucch_cfg); + if (pucch_res < 0) { + logger.debug("rnti={:#x}: Previously allocated PUCCH F1 resource can't be found in the PUCCH resource manager. " + "CSI allocation on F2 is aborted", + rnti, + pucch_slot_alloc.slot); + return; + } + format2_res.pucch_res_indicator = static_cast(pucch_res); + format2_res.pucch_res = resource_manager.reserve_specific_format2_res( + pucch_slot_alloc.slot, rnti, format2_res.pucch_res_indicator, pucch_cfg); + } else { + format2_res.pucch_res = resource_manager.reserve_csi_resource(pucch_slot_alloc.slot, rnti, ue_cell_cfg), + format2_res.pucch_res_indicator = 0; + } if (format2_res.pucch_res == nullptr) { logger.warning( @@ -968,18 +987,18 @@ pucch_harq_ack_grant pucch_allocator_impl::convert_to_format2_harq(cell_slot_res sr_nof_bits::no_sr, csi1_nof_bits_only_harq); - logger.debug( - "rnti={:#x}: PUCCH Format 2 grant allocation with {} H-ACK, {} SR, {} CSI bits for for slot={} completed", - rnti, - curr_harq_bits + harq_ack_bits_increment, - sr_nof_bits::no_sr, - csi1_nof_bits_only_harq, - pucch_slot_alloc.slot + logger.debug("rnti={:#x}: PUCCH Format 2 grant allocation with {} H-ACK, {} SR, {} CSI bits with res_ind={} for " + "slot={} completed", + rnti, + curr_harq_bits + harq_ack_bits_increment, + sr_nof_bits::no_sr, + csi1_nof_bits_only_harq, + format2_res.pucch_res_indicator, + pucch_slot_alloc.slot ); - return pucch_harq_ack_grant{.pucch_res_indicator = static_cast(format2_res.pucch_res_indicator), - .pucch_pdu = &pucch_pdu}; + return pucch_harq_ack_grant{.pucch_res_indicator = format2_res.pucch_res_indicator, .pucch_pdu = &pucch_pdu}; } pucch_harq_ack_grant pucch_allocator_impl::change_format2_resource(cell_slot_resource_allocator& pucch_slot_alloc, @@ -1098,7 +1117,10 @@ pucch_harq_ack_grant pucch_allocator_impl::add_harq_ack_bit_to_format1_grant(puc output.pucch_pdu = &existing_harq_grant; output.pucch_res_indicator = pucch_res_idx; - logger.debug("ue={:#x}: HARQ-ACK mltplxd on existing PUCCH for slot={}", rnti, sl_tx); + logger.debug("ue={:#x}: HARQ-ACK mltplxd on existing PUCCH F1 with res_ind={} for slot={}", + rnti, + output.pucch_res_indicator, + sl_tx); return output; } @@ -1114,8 +1136,8 @@ void pucch_allocator_impl::remove_pucch_format1_from_grants(cell_slot_resource_a if (not is_fallback_mode) { // Remove HARQ-ACK grant first. auto* it_harq = std::find_if(pucchs.begin(), pucchs.end(), [crnti](pucch_info& pucch) { - return pucch.crnti == crnti and pucch.format_1.sr_bits == sr_nof_bits::no_sr and - pucch.format_1.harq_ack_nof_bits > 0; + return pucch.crnti == crnti and pucch.format == pucch_format::FORMAT_1 and + pucch.format_1.sr_bits == sr_nof_bits::no_sr and pucch.format_1.harq_ack_nof_bits > 0; }); if (it_harq != pucchs.end()) { @@ -1126,7 +1148,8 @@ void pucch_allocator_impl::remove_pucch_format1_from_grants(cell_slot_resource_a // Remove SR grant, if any. auto* it_sr = std::find_if(pucchs.begin(), pucchs.end(), [crnti](pucch_info& pucch) { - return pucch.crnti == crnti and pucch.format_1.sr_bits == sr_nof_bits::one; + return pucch.crnti == crnti and pucch.format == pucch_format::FORMAT_1 and + pucch.format_1.sr_bits == sr_nof_bits::one; }); if (it_sr != pucchs.end()) { @@ -1143,7 +1166,8 @@ void pucch_allocator_impl::remove_format2_csi_from_grants(cell_slot_resource_all // Remove PUCCH Format2 resource specific for CSI. auto* it = std::find_if(pucchs.begin(), pucchs.end(), [crnti](pucch_info& pucch) { - return pucch.crnti == crnti and pucch.format_2.harq_ack_nof_bits == 0 and pucch.format_2.csi_part1_bits > 0; + return pucch.crnti == crnti and pucch.format == pucch_format::FORMAT_2 and pucch.format_2.harq_ack_nof_bits == 0 and + pucch.format_2.csi_part1_bits > 0; }); if (it != pucchs.end()) { @@ -1426,14 +1450,23 @@ pucch_harq_ack_grant pucch_allocator_impl::add_uci_bits_to_harq_f2_grant(pucch_i existing_f2_grant.format_2.csi_part1_bits += csi_part1_bits_increment; if (harq_ack_bits_increment != 0) { - logger.debug("rnti={:#x}: HARQ-ACK multiplexed on existing PUCCH F2 for slot={}", existing_f2_grant.crnti, sl_tx); + logger.debug("rnti={:#x}: HARQ-ACK multiplexed on existing PUCCH F2 with res_ind={} for slot={}", + existing_f2_grant.crnti, + pucch_f2_harq_cfg.pucch_res_indicator, + sl_tx); } else if (sr_bits_increment != sr_nof_bits::no_sr) { - logger.debug("rnti={:#x}: SR multiplexed on existing PUCCH F2 for slot={}", existing_f2_grant.crnti, sl_tx); + logger.debug("rnti={:#x}: SR multiplexed on existing PUCCH F2 with res_ind={} for slot={}", + existing_f2_grant.crnti, + pucch_f2_harq_cfg.pucch_res_indicator, + sl_tx); } else { - logger.debug("rnti={:#x}: CSI multiplexed on existing PUCCH F2 for slot={}", existing_f2_grant.crnti, sl_tx); + logger.debug("rnti={:#x}: CSI multiplexed on existing PUCCH F2 with res_ind={} for slot={}", + existing_f2_grant.crnti, + pucch_f2_harq_cfg.pucch_res_indicator, + sl_tx); } - return pucch_harq_ack_grant{.pucch_res_indicator = static_cast(pucch_f2_harq_cfg.pucch_res_indicator), + return pucch_harq_ack_grant{.pucch_res_indicator = pucch_f2_harq_cfg.pucch_res_indicator, .pucch_pdu = &existing_f2_grant}; } diff --git a/lib/scheduler/scheduler_impl.cpp b/lib/scheduler/scheduler_impl.cpp index 141428049c..8d6eb84bca 100644 --- a/lib/scheduler/scheduler_impl.cpp +++ b/lib/scheduler/scheduler_impl.cpp @@ -126,7 +126,8 @@ void scheduler_impl::handle_ul_phr_indication(const ul_phr_indication_message& p srsran_assert(cells.contains(phr_ind.cell_index), "cell={} does not exist", phr_ind.cell_index); // Early return if UE has not been created in the scheduler. - if (phr_ind.ue_index != INVALID_DU_UE_INDEX) { + if (phr_ind.ue_index == INVALID_DU_UE_INDEX) { + logger.warning("ue={}: Discarding UL PHR. Cause: UE Id is not valid", INVALID_DU_UE_INDEX); return; } diff --git a/lib/scheduler/pdcch_scheduling/pdcch_config_helpers.h b/lib/scheduler/support/pdcch/pdcch_mapping.h similarity index 96% rename from lib/scheduler/pdcch_scheduling/pdcch_config_helpers.h rename to lib/scheduler/support/pdcch/pdcch_mapping.h index adb50331da..3be15f81ea 100644 --- a/lib/scheduler/pdcch_scheduling/pdcch_config_helpers.h +++ b/lib/scheduler/support/pdcch/pdcch_mapping.h @@ -22,9 +22,9 @@ #pragma once -#include "../support/config_helpers.h" +#include "../config_helpers.h" +#include "srsran/ran/pdcch/aggregation_level.h" #include "srsran/ran/pdcch/cce_to_prb_mapping.h" -#include "srsran/ran/slot_point.h" #include "srsran/scheduler/config/bwp_configuration.h" namespace srsran { diff --git a/lib/scheduler/uci_scheduling/uci_allocator.h b/lib/scheduler/uci_scheduling/uci_allocator.h index d562dabbdc..bb46fd70a8 100644 --- a/lib/scheduler/uci_scheduling/uci_allocator.h +++ b/lib/scheduler/uci_scheduling/uci_allocator.h @@ -34,11 +34,8 @@ struct uci_allocation { pucch_harq_ack_grant pucch_grant; /// Delay in slots of the UE's PUCCH HARQ-ACK report with respect to the PDSCH. unsigned k1; - /// Downlink Assignment Index to be encoded in DL DCI when using the dynamic HARQ-ACK codebook, as per TS38.213 - /// Section 9.1.3. This counter informs the UE of the accumulated number of transmissions which require acknowledgment - /// up to the PDCCH monitoring occasion respective to this UCI allocation. The values wrap from 3 to 0, so four - /// consecutive missed resource allocations would be undetected. - uint8_t dai{0}; + /// Index of the HARQ-bit in the PUCCH/PUSCH HARQ report. + uint8_t harq_bit_idx{0}; }; /// \brief UCI allocator interface. diff --git a/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp b/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp index e84bd2e0b5..b99bcb9267 100644 --- a/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp +++ b/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp @@ -28,9 +28,6 @@ using namespace srsran; -/// Number of possible Downlink Assignment Indexes {0, ..., 3} as per TS38.213 Section 9.1.3. -constexpr static size_t DAI_MOD = 4; - //////////// C-tors and d-tors //////////// uci_allocator_impl::uci_allocator_impl(pucch_allocator& pucch_alloc_) : @@ -271,8 +268,8 @@ uci_allocation uci_allocator_impl::alloc_uci_harq_ue(cell_resource_allocator& uci->rnti = crnti; uci->scheduled_dl_pdcch_counter = 0; } - uci_output.dai = uci->scheduled_dl_pdcch_counter % DAI_MOD; - uci->scheduled_dl_pdcch_counter++; + uci_output.harq_bit_idx = uci->scheduled_dl_pdcch_counter; + ++uci->scheduled_dl_pdcch_counter; uci_output.k1 = k1_candidate; return uci_output; diff --git a/lib/scheduler/ue_scheduling/harq_process.cpp b/lib/scheduler/ue_scheduling/harq_process.cpp index c2b48be525..8672066f5e 100644 --- a/lib/scheduler/ue_scheduling/harq_process.cpp +++ b/lib/scheduler/ue_scheduling/harq_process.cpp @@ -50,68 +50,73 @@ detail::harq_process::harq_process(harq_id_t h_id, harq_logger& logger_, ue_harq_timeout_notifier timeout_notif, unsigned max_ack_wait_in_slots_) : - id(h_id), - logger(logger_), - timeout_notifier(timeout_notif), - max_ack_wait_in_slots(max_ack_wait_in_slots_), - ack_wait_in_slots(max_ack_wait_in_slots_) + id(h_id), logger(logger_), timeout_notifier(timeout_notif), max_ack_wait_in_slots(max_ack_wait_in_slots_) { } template void detail::harq_process::slot_indication(slot_point slot_tx) { + last_slot_ind = slot_tx; for (transport_block& tb : tb_array) { if (tb.state == transport_block::state_t::empty) { continue; } - if (last_slot_ack + ack_wait_in_slots > slot_tx) { + if (slot_ack_timeout > last_slot_ind) { // Wait more slots for ACK/NACK to arrive. return; } + if (tb.state == transport_block::state_t::pending_retx) { + // [Implementation-defined] Maximum time we give to the scheduler policy to retransmit a HARQ process. + const unsigned max_nof_slots_for_retx = last_slot_ack.nof_slots_per_system_frame() / 4; + if (slot_tx >= (last_slot_ack + max_nof_slots_for_retx)) { + // If a HARQ retx is never scheduled, the HARQ process will never be cleared. This is a safety mechanism to + // account for a potential bug or limitation in the scheduler policy that is leaving HARQ processes with + // pending reTxs for too long. + tb.state = transport_block::state_t::empty; + logger.warning( + id, + "Discarding HARQ. Cause: Too much time has passed since the last HARQ transmission. The scheduler " + "policy is likely not prioritizing retransmissions of old HARQ processes."); + } + continue; + } + + // HARQ-ACK is waiting for ACK. + + if (tb.ack_on_timeout) { + // Case: Not all HARQ-ACKs were received, but at least one positive ACK was received. + tb.state = transport_block::state_t::empty; + logger.debug(id, + "Setting HARQ to \"ACKed\" state. Cause: HARQ-ACK wait timeout ({} slots) was reached with still " + "missing PUCCH HARQ-ACKs. However, one positive ACK was received.", + slot_ack_timeout - last_slot_ack); + continue; + } + const bool max_retx_exceeded = tb.nof_retxs + 1 > tb.max_nof_harq_retxs; - if (tb.state == transport_block::state_t::waiting_ack and not max_retx_exceeded) { - // ACK went missing. + + if (not max_retx_exceeded) { + // ACK went missing, and we only have received NACK/DTX. tb.state = transport_block::state_t::pending_retx; - if (ack_wait_in_slots == max_ack_wait_in_slots) { - logger.warning(id, - "Setting HARQ to \"pending reTx\" state. Cause: HARQ-ACK wait timeout ({} slots) was reached " - "but no HARQ-ACK report was received.", - ack_wait_in_slots); - } else { - logger.debug(id, - "Setting HARQ to \"pending reTx\" state. Cause: HARQ-ACK wait timeout ({} slots) was reached " - "but only invalid HARQ-ACKs were received so far.", - ack_wait_in_slots); - } - timeout_notifier.notify_harq_timeout(IsDownlink); - } else if (max_retx_exceeded) { - // Max number of reTxs was exceeded. Clear HARQ process + logger.warning(id, + "Setting HARQ to \"pending reTx\" state. Cause: HARQ-ACK wait timeout ({} slots) was reached, " + "but there are still missing HARQ-ACKs and none of the received are positive.", + slot_ack_timeout - last_slot_ack); + } else { + // Max number of reTxs was exceeded. Clear HARQ process. tb.state = transport_block::state_t::empty; - if (ack_wait_in_slots == max_ack_wait_in_slots) { - logger.warning(id, - "Discarding HARQ. Cause: HARQ-ACK wait timeout ({} slots) was reached without a HARQ-ACK report " - "being received and the maximum number of reTxs {} was exceeded", - ack_wait_in_slots, - max_nof_harq_retxs(0)); - } else { - logger.debug(id, - "Discarding HARQ. Cause: HARQ-ACK wait timeout ({} slots) was reached but only invalid HARQ-ACKs " - "were received and the maximum number of reTxs {} was exceeded", - ack_wait_in_slots, - max_nof_harq_retxs(0)); - } - timeout_notifier.notify_harq_timeout(IsDownlink); - } else if (slot_tx >= (last_slot_ack + last_slot_ack.nof_slots_per_system_frame() / 4)) { - // If a HARQ retx is never scheduled, the HARQ process will never be cleared. This is a safety mechanism to - // account a potential bug or limitation in the scheduler policy. logger.warning(id, - "Discarding HARQ. Cause: Too much time has passed since the last HARQ transmission. The scheduler " - "policy is likely not prioritizing retransmissions of old HARQ processes."); + "Discarding HARQ. Cause: HARQ-ACK wait timeout ({} slots) was reached, but there are still " + "missing HARQ-ACKs, none of the received so far are positive and the maximum number of reTxs {} " + "was exceeded", + slot_ack_timeout - last_slot_ack, + max_nof_harq_retxs(0)); } - // Reset the ACK wait time. - ack_wait_in_slots = max_ack_wait_in_slots; + + // Report timeout with NACK. + timeout_notifier.notify_harq_timeout(IsDownlink); } } @@ -141,7 +146,7 @@ void detail::harq_process::reset() } template -void detail::harq_process::stop_retransmissions(unsigned tb_idx) +void detail::harq_process::cancel_harq(unsigned tb_idx) { if (empty(tb_idx)) { return; @@ -152,20 +157,23 @@ void detail::harq_process::stop_retransmissions(unsigned tb_idx) template void detail::harq_process::reset_tb(unsigned tb_idx) { - tb_array[tb_idx].state = transport_block::state_t::empty; - tb_array[tb_idx].nof_retxs = 0; + tb_array[tb_idx].state = transport_block::state_t::empty; + tb_array[tb_idx].nof_retxs = 0; + tb_array[tb_idx].ack_on_timeout = false; } template void detail::harq_process::tx_common(slot_point slot_tx_, slot_point slot_ack_) { - last_slot_tx = slot_tx_; - last_slot_ack = slot_ack_; - ack_wait_in_slots = max_ack_wait_in_slots; + last_slot_tx = slot_tx_; + last_slot_ack = slot_ack_; + slot_ack_timeout = slot_ack_ + max_ack_wait_in_slots; } template -void detail::harq_process::new_tx_tb_common(unsigned tb_idx, unsigned max_nof_harq_retxs, uint8_t dai) +void detail::harq_process::new_tx_tb_common(unsigned tb_idx, + unsigned max_nof_harq_retxs, + uint8_t harq_bit_idx) { srsran_assert(tb_idx < tb_array.size(), "TB index is out-of-bounds"); srsran_assert(empty(tb_idx), "Cannot allocate newTx non-empty HARQ TB"); @@ -173,44 +181,59 @@ void detail::harq_process::new_tx_tb_common(unsigned tb_idx, unsigne tb_array[tb_idx].ndi = !tb_array[tb_idx].ndi; tb_array[tb_idx].max_nof_harq_retxs = max_nof_harq_retxs; tb_array[tb_idx].nof_retxs = 0; - tb_array[tb_idx].dai = dai; + tb_array[tb_idx].harq_bit_idx = harq_bit_idx; + tb_array[tb_idx].ack_on_timeout = false; } template -void detail::harq_process::new_retx_tb_common(unsigned tb_idx, uint8_t dai) +void detail::harq_process::new_retx_tb_common(unsigned tb_idx, uint8_t harq_bit_idx) { srsran_assert(tb_idx < tb_array.size(), "TB index is out-of-bounds"); srsran_assert(tb_array[tb_idx].state == transport_block::state_t::pending_retx, "Cannot allocate reTx in HARQ without a pending reTx"); - tb_array[tb_idx].state = transport_block::state_t::waiting_ack; - tb_array[tb_idx].dai = dai; + tb_array[tb_idx].state = transport_block::state_t::waiting_ack; + tb_array[tb_idx].harq_bit_idx = harq_bit_idx; tb_array[tb_idx].nof_retxs++; + tb_array[tb_idx].ack_on_timeout = false; } // Explicit template instantiation. template class detail::harq_process; template class detail::harq_process; -void dl_harq_process::new_tx(slot_point pdsch_slot, unsigned k1, unsigned max_harq_nof_retxs, uint8_t dai) +void dl_harq_process::new_tx(slot_point pdsch_slot, + unsigned k1, + unsigned max_harq_nof_retxs, + uint8_t harq_bit_idx, + cqi_value cqi, + unsigned nof_layers) { base_type::tx_common(pdsch_slot, pdsch_slot + k1); - base_type::new_tx_tb_common(0, max_harq_nof_retxs, dai); - prev_tx_params = {}; + base_type::new_tx_tb_common(0, max_harq_nof_retxs, harq_bit_idx); + prev_tx_params = {}; + prev_tx_params.cqi = cqi; + prev_tx_params.nof_layers = nof_layers; prev_tx_params.tb[0].emplace(); prev_tx_params.tb[1].reset(); + pucch_ack_to_receive = 0; + chosen_ack = mac_harq_ack_report_status::dtx; + last_pucch_snr = nullopt; } -void dl_harq_process::new_retx(slot_point pdsch_slot, unsigned k1, uint8_t dai) +void dl_harq_process::new_retx(slot_point pdsch_slot, unsigned k1, uint8_t harq_bit_idx) { base_type::tx_common(pdsch_slot, pdsch_slot + k1); - base_type::new_retx_tb_common(0, dai); + base_type::new_retx_tb_common(0, harq_bit_idx); + pucch_ack_to_receive = 0; + chosen_ack = mac_harq_ack_report_status::dtx; + last_pucch_snr = nullopt; } void dl_harq_process::tx_2_tb(slot_point pdsch_slot, unsigned k1, span tb_tx_req, unsigned max_harq_nof_retxs, - uint8_t dai) + uint8_t harq_bit_idx) { srsran_assert(tb_tx_req.size() == 2, "This function should only be called when 2 TBs are active"); srsran_assert( @@ -219,48 +242,50 @@ void dl_harq_process::tx_2_tb(slot_point pdsch_slot, base_type::tx_common(pdsch_slot, pdsch_slot + k1); for (unsigned i = 0; i != tb_tx_req.size(); ++i) { if (tb_tx_req[i] == tb_tx_request::newtx) { - base_type::new_tx_tb_common(i, max_harq_nof_retxs, dai); + base_type::new_tx_tb_common(i, max_harq_nof_retxs, harq_bit_idx); prev_tx_params.tb[i].emplace(); } else if (tb_tx_req[i] == tb_tx_request::retx) { - base_type::new_retx_tb_common(i, dai); + base_type::new_retx_tb_common(i, harq_bit_idx); } else { prev_tx_params.tb[i].reset(); } } } -bool dl_harq_process::ack_info(uint32_t tb_idx, mac_harq_ack_report_status ack) +bool dl_harq_process::ack_info(uint32_t tb_idx, mac_harq_ack_report_status ack, optional pucch_snr) { - if (ack == mac_harq_ack_report_status::dtx) { - // When receing a DTX for the TB_idx 0 that is waiting for an ACK, reduce the ack_wait_in_slots. - if (tb_idx == 0 and not empty(tb_idx) and tb(tb_idx).state == transport_block::state_t::waiting_ack) { - ack_wait_in_slots = SHORT_ACK_TIMEOUT_DTX; - } - return true; + if (not is_waiting_ack(tb_idx)) { + // If the HARQ process is not expected an HARQ-ACK anymore, it means that it has already been ACKed/NACKed. + return false; } - // If it is an ACK or NACK, check if the TB_idx is active. - if (empty(tb_idx)) { - logger.info(id, - "Discarding HARQ-ACK tb={} ack={}. Cause: HARQ process is not active", - tb_idx, - ack == mac_harq_ack_report_status::ack ? 1 : 0); - return false; + if (ack != mac_harq_ack_report_status::dtx and + (not last_pucch_snr.has_value() or (pucch_snr.has_value() and last_pucch_snr.value() < pucch_snr.value()))) { + // Case: If there was no previous HARQ-ACK decoded or the previous HARQ-ACK had lower SNR, this HARQ-ACK is chosen. + chosen_ack = ack; + last_pucch_snr = pucch_snr; } - // When receiving an ACK or NACK, reset the ack_wait_in_slots to the maximum value. - ack_wait_in_slots = max_ack_wait_in_slots; - - // From this point on, ack is either mac_harq_ack_report_status::ack or mac_harq_ack_report_status::nack; - base_type::ack_info_common(tb_idx, ack == mac_harq_ack_report_status::ack); - if (ack == mac_harq_ack_report_status::nack and empty(tb_idx)) { - logger.info(id, - "Discarding HARQ process tb={} with tbs={}. Cause: Maximum number of reTxs {} exceeded", - tb_idx, - prev_tx_params.tb[tb_idx]->tbs_bytes, - max_nof_harq_retxs(tb_idx), - ack_wait_in_slots); + if (pucch_ack_to_receive <= 1) { + // Case: This is the last HARQ-ACK that is expected for this HARQ process. + + base_type::ack_info_common(tb_idx, chosen_ack == mac_harq_ack_report_status::ack); + if (chosen_ack != mac_harq_ack_report_status::ack and empty(tb_idx)) { + logger.info(id, + "Discarding HARQ process tb={} with tbs={}. Cause: Maximum number of reTxs {} exceeded", + tb_idx, + prev_tx_params.tb[tb_idx]->tbs_bytes, + max_nof_harq_retxs(tb_idx)); + } + return true; } + + // Case: This is not the last PUCCH HARQ-ACK that is expected for this HARQ process. + pucch_ack_to_receive--; + tb_array[tb_idx].ack_on_timeout = chosen_ack == mac_harq_ack_report_status::ack; + // We reduce the HARQ process timeout to receive the next HARQ-ACK. This is done because the two HARQ-ACKs should + // arrive almost simultaneously, and we in case the second goes missing, we don't want to block the HARQ for too long. + slot_ack_timeout = last_slot_ind + SHORT_ACK_TIMEOUT_DTX; return true; } @@ -289,6 +314,11 @@ void dl_harq_process::save_alloc_params(dci_dl_rnti_config_type dci_cfg_type, co prev_tx_params.nof_symbols = pdsch.symbols.length(); } +void dl_harq_process::increment_pucch_counter() +{ + ++pucch_ack_to_receive; +} + void ul_harq_process::new_tx(slot_point pusch_slot, unsigned max_harq_retxs) { harq_process::tx_common(pusch_slot, pusch_slot); @@ -314,8 +344,7 @@ int ul_harq_process::crc_info(bool ack) logger.info(id, "Discarding HARQ with tbs={}. Cause: Maximum number of reTxs {} exceeded", prev_tx_params.tbs_bytes, - max_nof_harq_retxs(), - ack_wait_in_slots); + max_nof_harq_retxs()); } return 0; } @@ -338,9 +367,9 @@ void ul_harq_process::save_alloc_params(dci_ul_rnti_config_type dci_cfg_type, co prev_tx_params.rbs = pusch.rbs; } -void ul_harq_process::stop_retransmissions() +void ul_harq_process::cancel_harq() { - base_type::stop_retransmissions(0); + base_type::cancel_harq(0); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -377,45 +406,24 @@ void harq_entity::slot_indication(slot_point slot_tx_) } } -const dl_harq_process* harq_entity::dl_ack_info(slot_point uci_slot, mac_harq_ack_report_status ack, uint8_t dai) +const dl_harq_process* harq_entity::dl_ack_info(slot_point uci_slot, + mac_harq_ack_report_status ack, + uint8_t harq_bit_idx, + optional pucch_snr) { - srsran_assert(dai < 4, "DAI must be in range [0, 3]"); // For the time being, we assume 1 TB only. static const size_t tb_index = 0; - dl_harq_process* harq_candidate = nullptr; - bool harq_update_needed = false; for (dl_harq_process& h_dl : dl_harqs) { - if (h_dl.slot_ack() == uci_slot) { - if (h_dl.is_waiting_ack(tb_index) and h_dl.tb(0).dai == dai) { - // Update HARQ state and stop search. - h_dl.ack_info(tb_index, ack); + if (h_dl.slot_ack() == uci_slot and h_dl.tb(tb_index).harq_bit_idx == harq_bit_idx) { + // Update HARQ state. + if (h_dl.ack_info(tb_index, ack, pucch_snr)) { return &h_dl; } - if (ack == mac_harq_ack_report_status::ack and not h_dl.empty(tb_index) and h_dl.tb(0).dai == dai) { - // The HARQ might have been NACKed (false alarm from PHY) before. In this case, there is some ambiguity - // on whether this HARQ bit is for this HARQ or another. We save this candidate and continue searching. If no - // better candidate is found, we will update this HARQ. - harq_candidate = &h_dl; - harq_update_needed = true; - continue; - } - if (harq_candidate == nullptr) { - // Handle case when two HARQ-ACKs arrive for the same HARQ, and the first ACK empties the HARQ. - harq_candidate = &h_dl; - } } } - if (harq_candidate != nullptr and harq_update_needed) { - harq_candidate->ack_info(tb_index, ack); - } - if (harq_candidate == nullptr and ack != mac_harq_ack_report_status::dtx) { - // Note: In the situations when two PUCCH PDUs are scheduled for the same slot, it can happen that the first PDU - // empties the HARQ with an ACK and the HARQ cannot be found anymore by the second HARQ PDU. In such situation, - // avoid this warning. - logger.warning("DL HARQ for rnti={:#x}, uci slot={} not found.", rnti, uci_slot); - } - return harq_candidate; + logger.warning("DL HARQ for rnti={:#x}, uci slot={} not found.", rnti, uci_slot); + return nullptr; } int harq_entity::ul_crc_info(harq_id_t h_id, bool ack, slot_point pusch_slot) diff --git a/lib/scheduler/ue_scheduling/harq_process.h b/lib/scheduler/ue_scheduling/harq_process.h index a4fd8529de..36fb80b3b3 100644 --- a/lib/scheduler/ue_scheduling/harq_process.h +++ b/lib/scheduler/ue_scheduling/harq_process.h @@ -23,6 +23,7 @@ #pragma once #include "srsran/adt/static_vector.h" +#include "srsran/ran/csi_report/csi_report_data.h" #include "srsran/ran/pdsch/pdsch_mcs.h" #include "srsran/ran/pusch/pusch_mcs.h" #include "srsran/ran/rnti.h" @@ -130,7 +131,7 @@ class harq_process /// (implementation-defined). constexpr static unsigned DEFAULT_ACK_TIMEOUT_SLOTS = 256U; - constexpr static unsigned SHORT_ACK_TIMEOUT_DTX = 10U; + constexpr static unsigned SHORT_ACK_TIMEOUT_DTX = 4U; /// Maximum number of Transport Blocks as per TS38.321, 5.3.2.1 and 5.4.2.1. constexpr static size_t MAX_NOF_TBS = IsDownlink ? 2 : 1; @@ -148,8 +149,11 @@ class harq_process /// Maximum number of retransmission before Transport Block is reset. unsigned max_nof_harq_retxs = 0; - /// Downlink Assignment Index used in case of PDSCH. - uint8_t dai; + /// HARQ-bit index corresponding to this HARQ process in the UCI PDU indication. + uint8_t harq_bit_idx = 0; + + /// Whether to set the HARQ as ACKed or NACKed when the timeout expires. + bool ack_on_timeout = false; bool empty() const { return state == state_t::empty; } }; @@ -202,22 +206,18 @@ class harq_process } /// \brief Getter for TB parameters. - const transport_block& tb(unsigned tb_idx) const - { - srsran_assert(not empty(tb_idx), "TB {} is not active", tb_idx); - return tb_array[tb_idx]; - } + const transport_block& tb(unsigned tb_idx) const { return tb_array[tb_idx]; } /// \brief Resets HARQ process state. void reset(); - /// Forbids the HARQ from retransmitting the specified TB until the next new transmission. - void stop_retransmissions(unsigned tb_idx); + /// Cancels the HARQ and stops retransmitting the specified TB until the next new transmission. + void cancel_harq(unsigned tb_idx); protected: void tx_common(slot_point slot_tx, slot_point slot_ack); - void new_tx_tb_common(unsigned tb_idx, unsigned max_nof_harq_retxs, uint8_t dai); - void new_retx_tb_common(unsigned tb_idx, uint8_t dai); + void new_tx_tb_common(unsigned tb_idx, unsigned max_nof_harq_retxs, uint8_t harq_bit_idx); + void new_retx_tb_common(unsigned tb_idx, uint8_t harq_bit_idx); /// \brief Updates the ACK state of the TB of the HARQ process. bool ack_info_common(uint32_t tb_idx, bool ack); @@ -234,10 +234,8 @@ class harq_process /// Maximum value of time interval, in slots, before the HARQ process assumes that the ACK/CRC went missing. const unsigned max_ack_wait_in_slots; - /// Actual time interval, in slots, before the HARQ process assumes that the ACK/CRC went missing. - /// This value is shorten dynamically when the MAC returns a ACK with DTX (not correctly decoded) state; it gets - /// re-set to its maximum value at the beginning of each transmission or retx, and after receiving a ACK or NACK. - unsigned ack_wait_in_slots; + /// Last slot indication. + slot_point last_slot_ind; /// For DL, slot_tx corresponds to the slot when the TB in the HARQ process is going to be transmitted by the gNB. /// For UL, slot_tx corresponds to the slot when the TB in the HARQ process is going to be transmitted by the UE. @@ -247,6 +245,9 @@ class harq_process /// For UL, slot_ack is the slot when the PUSCH CRC will be received by the gNB. It coincides with slot_tx of UL. slot_point last_slot_ack; + /// Slot when the HARQ process will assume that the ACK/CRC went missing. + slot_point slot_ack_timeout; + std::array tb_array; }; @@ -269,6 +270,8 @@ class dl_harq_process : public detail::harq_process vrb_alloc rbs; unsigned nof_symbols; std::array, base_type::MAX_NOF_TBS> tb; + cqi_value cqi; + unsigned nof_layers; }; using base_type::transport_block; @@ -292,11 +295,16 @@ class dl_harq_process : public detail::harq_process /// \brief Called on every new TB transmission, when only one TB is active. It marks this HARQ process as busy and /// stores respective TB information. - void new_tx(slot_point pdsch_slot, unsigned k1, unsigned max_harq_nof_retxs, uint8_t dai); + void new_tx(slot_point pdsch_slot, + unsigned k1, + unsigned max_harq_nof_retxs, + uint8_t harq_bit_idx, + cqi_value cqi, + unsigned nof_layers); /// \brief Called on every TB retransmission, when only one TB is active. This function assumes that the HARQ TB is /// in pending new_retx state. - void new_retx(slot_point pdsch_slot, unsigned k1, uint8_t dai); + void new_retx(slot_point pdsch_slot, unsigned k1, uint8_t harq_bit_idx); /// \brief Called on every new TB transmission/retransmission, when 2 TBs are used. enum class tb_tx_request { newtx, retx, disabled }; @@ -304,19 +312,27 @@ class dl_harq_process : public detail::harq_process unsigned k1, span tb_tx_req, unsigned max_harq_nof_retxs, - uint8_t dai); + uint8_t harq_bit_idx); /// \brief Updates the ACK state of the HARQ process. /// \return True if harq was not empty and state was succesfully updated. False, otherwise. - bool ack_info(uint32_t tb_idx, mac_harq_ack_report_status ack); + bool ack_info(uint32_t tb_idx, mac_harq_ack_report_status ack, optional pucch_snr); /// \brief Stores grant parameters that are associated with the HARQ allocation (e.g. DCI format, PRBs, MCS) so that /// they can be later fetched and optionally reused. void save_alloc_params(dci_dl_rnti_config_type dci_cfg_type, const pdsch_information& pdsch); + void increment_pucch_counter(); + private: /// Parameters used for the last Tx of this HARQ process. alloc_params prev_tx_params; + /// Keeps the count of how many PUCCH grants are allocate for this harq_process. + unsigned pucch_ack_to_receive{0}; + /// Chosen ACK status for this HARQ process transmission, given one or more HARQ-ACK bits received. + mac_harq_ack_report_status chosen_ack = mac_harq_ack_report_status::dtx; + /// Stores the highest recorded PUCCH SNR for this HARQ process. + optional last_pucch_snr; }; class ul_harq_process : private detail::harq_process @@ -375,8 +391,8 @@ class ul_harq_process : private detail::harq_process /// they can be later fetched and optionally reused. void save_alloc_params(dci_ul_rnti_config_type dci_cfg_type, const pusch_information& pusch); - /// Forbids the HARQ from retransmitting the specified TB until the next new transmission. - void stop_retransmissions(); + /// Cancels the HARQ and stops retransmitting the specified TB until the next new transmission. + void cancel_harq(); private: /// Parameters used for the last Tx of this HARQ process. @@ -415,7 +431,8 @@ class harq_entity /// \brief Update the state of the DL HARQ for the specified UCI slot. /// \return HARQ process whose state was updated. Nullptr, if no HARQ for which the ACK/NACK was directed was found. - const dl_harq_process* dl_ack_info(slot_point uci_slot, mac_harq_ack_report_status ack, uint8_t dai); + const dl_harq_process* + dl_ack_info(slot_point uci_slot, mac_harq_ack_report_status ack, uint8_t harq_bit_idx, optional pucch_snr); /// Update UL HARQ state given the received CRC indication. /// \return Transport Block size of the HARQ whose state was updated. @@ -479,6 +496,20 @@ class harq_entity return h_id == INVALID_HARQ_ID ? nullptr : &ul_harqs[h_id]; } + dl_harq_process* find_dl_harq_waiting_ack_slot(slot_point sl_ack, unsigned harq_bit_idx) + { + // For the time being, we assume 1 TB only. + static const size_t tb_index = 0; + + for (unsigned i = 0; i != dl_harqs.size(); ++i) { + if (dl_harqs[i].tb(tb_index).harq_bit_idx == harq_bit_idx and dl_harqs[i].is_waiting_ack(tb_index) and + dl_harqs[i].slot_ack() == sl_ack) { + return &dl_harqs[to_harq_id(i)]; + } + } + return nullptr; + } + private: template harq_id_t find_oldest_harq_retx(const HarqVector& harqs) const diff --git a/lib/scheduler/ue_scheduling/ue.cpp b/lib/scheduler/ue_scheduling/ue.cpp index 7f63fd4fd3..c06c57e0f6 100644 --- a/lib/scheduler/ue_scheduling/ue.cpp +++ b/lib/scheduler/ue_scheduling/ue.cpp @@ -77,7 +77,7 @@ void ue::deactivate() for (unsigned i = 0; i != ue_du_cells.size(); ++i) { if (ue_du_cells[i] != nullptr) { for (unsigned hid = 0; hid != ue_du_cells[i]->harqs.nof_ul_harqs(); ++hid) { - ue_du_cells[i]->harqs.ul_harq(hid).stop_retransmissions(); + ue_du_cells[i]->harqs.ul_harq(hid).cancel_harq(); } } } @@ -179,6 +179,11 @@ unsigned ue::pending_ul_newtx_bytes() const return pending_bytes > 0 ? pending_bytes : (ul_lc_ch_mgr.has_pending_sr() ? SR_GRANT_BYTES : 0); } +bool ue::has_pending_sr() const +{ + return ul_lc_ch_mgr.has_pending_sr(); +} + unsigned ue::build_dl_transport_block_info(dl_msg_tb_info& tb_info, unsigned tb_size_bytes) { unsigned total_subpdu_bytes = 0; diff --git a/lib/scheduler/ue_scheduling/ue.h b/lib/scheduler/ue_scheduling/ue.h index 1daef68838..5526f1e101 100644 --- a/lib/scheduler/ue_scheduling/ue.h +++ b/lib/scheduler/ue_scheduling/ue.h @@ -143,6 +143,9 @@ class ue /// to derive the required transport block size for an UL grant. unsigned pending_ul_newtx_bytes() const; + /// \brief Returns whether a SR indication handling is pending. + bool has_pending_sr() const; + /// \brief Defines the list of subPDUs, including LCID and payload size, that will compose the transport block. /// \return Returns the number of bytes reserved in the TB for subPDUs (other than padding). /// \remark Excludes SRB0. diff --git a/lib/scheduler/ue_scheduling/ue_cell.cpp b/lib/scheduler/ue_scheduling/ue_cell.cpp index 577aa81e93..9b34ebe7cd 100644 --- a/lib/scheduler/ue_scheduling/ue_cell.cpp +++ b/lib/scheduler/ue_scheduling/ue_cell.cpp @@ -44,6 +44,7 @@ ue_cell::ue_cell(du_ue_index_t ue_index_, crnti_(crnti_val), cell_cfg(cell_cfg_common_), ue_cfg(crnti_val, cell_cfg_common_, ue_serv_cell), + expert_cfg(cell_cfg.expert_cfg.ue), logger(srslog::fetch_basic_logger("SCHED")), channel_state(cell_cfg.expert_cfg.ue, ue_cfg.get_nof_dl_ports()), ue_mcs_calculator(cell_cfg_common_, channel_state) @@ -60,12 +61,12 @@ void ue_cell::handle_resource_allocation_reconfiguration_request(const sched_ue_ ue_res_alloc_cfg = ra_cfg; } -const dl_harq_process* -ue_cell::handle_dl_ack_info(slot_point uci_slot, mac_harq_ack_report_status ack_value, unsigned harq_bit_idx) +const dl_harq_process* ue_cell::handle_dl_ack_info(slot_point uci_slot, + mac_harq_ack_report_status ack_value, + unsigned harq_bit_idx, + optional pucch_snr) { - static constexpr unsigned dai_mod = 4; - - const dl_harq_process* h_dl = harqs.dl_ack_info(uci_slot, ack_value, harq_bit_idx % dai_mod); + const dl_harq_process* h_dl = harqs.dl_ack_info(uci_slot, ack_value, harq_bit_idx, pucch_snr); if (h_dl != nullptr) { // Consider the feedback in the link adaptation controller. @@ -191,13 +192,22 @@ int ue_cell::handle_crc_pdu(slot_point pusch_slot, const ul_crc_pdu_indication& return tbs; } +void ue_cell::handle_csi_report(const csi_report_data& csi_report) +{ + set_fallback_state(false); + apply_link_adaptation_procedures(csi_report); + if (not channel_state.handle_csi_report(csi_report)) { + logger.warning("ue={} rnti={:#x}: Invalid CSI report received", ue_index, rnti()); + } +} + template static static_vector get_prioritized_search_spaces(const ue_cell& ue_cc, FilterSearchSpace filter, bool is_dl) { static_vector active_search_spaces; - // Get all search Spaces for active BWP. + // Get all Search Spaces configured in PDCCH-Config for active BWP. const auto& bwp_ss_lst = ue_cc.cfg().bwp(ue_cc.active_bwp_id()).search_spaces; for (const search_space_info* search_space : bwp_ss_lst) { if (filter(*search_space)) { @@ -206,17 +216,11 @@ get_prioritized_search_spaces(const ue_cell& ue_cc, FilterSearchSpace filter, bo } // Sort search spaces by priority. - // TODO: Revisit SearchSpace prioritization. auto sort_ss = [&ue_cc, is_dl](const search_space_info* lhs, const search_space_info* rhs) { // NOTE: It does not matter whether we use lhs or rhs SearchSpace to get the aggregation level as we are sorting not // filtering. Filtering is already done in previous step. const unsigned aggr_lvl_idx = to_aggregation_level_index( ue_cc.get_aggregation_level(ue_cc.channel_state_manager().get_wideband_cqi(), *lhs, is_dl)); - if (lhs->cfg->get_nof_candidates()[aggr_lvl_idx] == rhs->cfg->get_nof_candidates()[aggr_lvl_idx]) { - // In case nof. candidates are equal, choose the SS with higher CORESET Id (i.e. try to use CORESET#0 as - // little as possible). - return lhs->cfg->get_coreset_id() > rhs->cfg->get_coreset_id(); - } return lhs->cfg->get_nof_candidates()[aggr_lvl_idx] > rhs->cfg->get_nof_candidates()[aggr_lvl_idx]; }; std::sort(active_search_spaces.begin(), active_search_spaces.end(), sort_ss); @@ -250,6 +254,40 @@ ue_cell::get_active_dl_search_spaces(slot_point pdcch_slo } auto filter_ss = [this, pdcch_slot, required_dci_rnti_type](const search_space_info& ss) { + // See TS 38.213, A UE monitors PDCCH candidates in one or more of the following search spaces sets: + // - a Type3-PDCCH CSS set configured by SearchSpace in PDCCH-Config with searchSpaceType = common for DCI formats + // with CRC scrambled by INT-RNTI, SFI-RNTI, TPC-PUSCH-RNTI, TPC-PUCCH-RNTI, or TPC-SRS-RNTI and, only for the + // primary cell, C-RNTI, MCS-C-RNTI, or CS-RNTI(s). + // - a USS set configured by SearchSpace in PDCCH-Config with searchSpaceType = ue-Specific for DCI formats + // with CRC scrambled by C-RNTI, MCS-C-RNTI, SP-CSI-RNTI, or CS-RNTI(s). + // + // As per TS 38.213, the UE monitors PDCCH candidates for DCI format 0_0 and DCI format 1_0 with CRC scrambled by + // the C-RNTI, the MCS-C-RNTI, or the CS-RNTI in the one or more search space sets in a slot where the UE monitors + // PDCCH candidates for at least a DCI format 0_0 or a DCI format 1_0 with CRC scrambled by SI-RNTI, RA-RNTI or + // P-RNTI. + if (ss.cfg->is_common_search_space()) { + const auto& pdcch_config_ss_lst = cfg().bwp(active_bwp_id()).dl_ded->pdcch_cfg->search_spaces; + const bool is_type3_css = std::find_if(pdcch_config_ss_lst.begin(), + pdcch_config_ss_lst.end(), + [&ss](const search_space_configuration& ss_cfg) { + return ss.cfg->get_id() == ss_cfg.get_id(); + }) != pdcch_config_ss_lst.end(); + + const bool is_ss_for_ra = + ss.cfg->get_id() == cfg().cell_cfg_common.dl_cfg_common.init_dl_bwp.pdcch_common.ra_search_space_id; + const bool is_ss_for_paging = + not cfg().cell_cfg_common.dl_cfg_common.init_dl_bwp.pdcch_common.paging_search_space_id.has_value() or + ss.cfg->get_id() == + cfg().cell_cfg_common.dl_cfg_common.init_dl_bwp.pdcch_common.paging_search_space_id.value(); + + // [Implementation-defined] We exclude SearchSpace#0 to avoid the complexity of computing the SearchSpace#0 PDCCH + // candidates monitoring occasions associated with a SS/PBCH block as mentioned in TS 38.213, clause 10.1. + if (ss.cfg->get_id() == to_search_space_id(0) or + (not is_ss_for_ra and not is_ss_for_paging and not is_type3_css)) { + return false; + } + } + if (ss.get_pdcch_candidates(get_aggregation_level(channel_state_manager().get_wideband_cqi(), ss, true), pdcch_slot) .empty()) { return false; @@ -280,7 +318,46 @@ ue_cell::get_active_ul_search_spaces(slot_point pdcch_slo return active_search_spaces; } - auto filter_ss = [required_dci_rnti_type](const search_space_info& ss) { + auto filter_ss = [this, pdcch_slot, required_dci_rnti_type](const search_space_info& ss) { + // See TS 38.213, A UE monitors PDCCH candidates in one or more of the following search spaces sets: + // - a Type3-PDCCH CSS set configured by SearchSpace in PDCCH-Config with searchSpaceType = common for DCI formats + // with CRC scrambled by INT-RNTI, SFI-RNTI, TPC-PUSCH-RNTI, TPC-PUCCH-RNTI, or TPC-SRS-RNTI and, only for the + // primary cell, C-RNTI, MCS-C-RNTI, or CS-RNTI(s). + // - a USS set configured by SearchSpace in PDCCH-Config with searchSpaceType = ue-Specific for DCI formats + // with CRC scrambled by C-RNTI, MCS-C-RNTI, SP-CSI-RNTI, or CS-RNTI(s). + // + // As per TS 38.213, the UE monitors PDCCH candidates for DCI format 0_0 and DCI format 1_0 with CRC scrambled by + // the C-RNTI, the MCS-C-RNTI, or the CS-RNTI in the one or more search space sets in a slot where the UE monitors + // PDCCH candidates for at least a DCI format 0_0 or a DCI format 1_0 with CRC scrambled by SI-RNTI, RA-RNTI or + // P-RNTI. + if (ss.cfg->is_common_search_space()) { + const auto& pdcch_config_ss_lst = cfg().bwp(active_bwp_id()).dl_ded->pdcch_cfg->search_spaces; + const bool is_type3_css = std::find_if(pdcch_config_ss_lst.begin(), + pdcch_config_ss_lst.end(), + [&ss](const search_space_configuration& ss_cfg) { + return ss.cfg->get_id() == ss_cfg.get_id(); + }) != pdcch_config_ss_lst.end(); + + const bool is_ss_for_ra = + ss.cfg->get_id() == cfg().cell_cfg_common.dl_cfg_common.init_dl_bwp.pdcch_common.ra_search_space_id; + const bool is_ss_for_paging = + not cfg().cell_cfg_common.dl_cfg_common.init_dl_bwp.pdcch_common.paging_search_space_id.has_value() or + ss.cfg->get_id() == + cfg().cell_cfg_common.dl_cfg_common.init_dl_bwp.pdcch_common.paging_search_space_id.value(); + + // [Implementation-defined] We exclude SearchSpace#0 to avoid the complexity of computing the SearchSpace#0 PDCCH + // candidates monitoring occasions associated with a SS/PBCH block as mentioned in TS 38.213, clause 10.1. + if (ss.cfg->get_id() == to_search_space_id(0) or + (not is_ss_for_ra and not is_ss_for_paging and not is_type3_css)) { + return false; + } + } + + if (ss.get_pdcch_candidates(get_aggregation_level(channel_state_manager().get_wideband_cqi(), ss, false), + pdcch_slot) + .empty()) { + return false; + } return (not required_dci_rnti_type.has_value() or *required_dci_rnti_type == (ss.get_ul_dci_format() == dci_ul_format::f0_0 ? dci_ul_rnti_config_type::c_rnti_f0_0 @@ -318,3 +395,42 @@ aggregation_level ue_cell::get_aggregation_level(cqi_value cqi, const search_spa return map_cqi_to_aggregation_level(cqi, cqi_table, ss_info.cfg->get_nof_candidates(), dci_size); } + +void ue_cell::apply_link_adaptation_procedures(const csi_report_data& csi_report) +{ + // Early return if no decrease in CQI and RI. + const bool cqi_decreased = csi_report.first_tb_wideband_cqi.has_value() and + csi_report.first_tb_wideband_cqi.value() < channel_state.get_wideband_cqi(); + const bool ri_decreased = csi_report.ri.has_value() and csi_report.ri.value() < channel_state.get_nof_dl_layers(); + if (not cqi_decreased and not ri_decreased) { + return; + } + + const csi_report_wideband_cqi_type wideband_cqi = csi_report.first_tb_wideband_cqi.has_value() + ? csi_report.first_tb_wideband_cqi.value() + : channel_state.get_wideband_cqi(); + const unsigned recommended_dl_layers = + csi_report.ri.has_value() and csi_report.ri.value() <= ue_cfg.get_nof_dl_ports() + ? csi_report.ri->value() + : channel_state.get_nof_dl_layers(); + + static const uint8_t tb_index = 0; + // Link adaptation for HARQs. + // [Implementation-defined] If the drop in RI or CQI when compared to the RI or CQI at the time of new HARQ + // transmission is above threshold then HARQ re-transmissions are cancelled. + for (unsigned hid = 0; hid != harqs.nof_dl_harqs(); ++hid) { + dl_harq_process& h_dl = harqs.dl_harq(hid); + if (h_dl.empty()) { + continue; + } + const bool is_ri_diff_above_threshold = + expert_cfg.dl_harq_la_ri_drop_threshold != 0 and + recommended_dl_layers + expert_cfg.dl_harq_la_ri_drop_threshold <= h_dl.last_alloc_params().nof_layers; + const bool is_cqi_diff_above_threshold = + expert_cfg.dl_harq_la_cqi_drop_threshold != 0 and + wideband_cqi.to_uint() + expert_cfg.dl_harq_la_cqi_drop_threshold <= h_dl.last_alloc_params().cqi.to_uint(); + if (is_ri_diff_above_threshold or is_cqi_diff_above_threshold) { + h_dl.cancel_harq(tb_index); + } + } +} diff --git a/lib/scheduler/ue_scheduling/ue_cell.h b/lib/scheduler/ue_scheduling/ue_cell.h index 9cd5cc70df..a0f2fc1cdf 100644 --- a/lib/scheduler/ue_scheduling/ue_cell.h +++ b/lib/scheduler/ue_scheduling/ue_cell.h @@ -71,8 +71,10 @@ class ue_cell void handle_reconfiguration_request(const serving_cell_config& new_ue_cell_cfg); void handle_resource_allocation_reconfiguration_request(const sched_ue_resource_alloc_config& ra_cfg); - const dl_harq_process* - handle_dl_ack_info(slot_point uci_slot, mac_harq_ack_report_status ack_value, unsigned harq_bit_idx); + const dl_harq_process* handle_dl_ack_info(slot_point uci_slot, + mac_harq_ack_report_status ack_value, + unsigned harq_bit_idx, + optional pucch_snr); /// \brief Estimate the number of required DL PRBs to allocate the given number of bytes. grant_prbs_mcs required_dl_prbs(const pdsch_time_domain_resource_allocation& pdsch_td_cfg, @@ -101,13 +103,7 @@ class ue_cell int handle_crc_pdu(slot_point pusch_slot, const ul_crc_pdu_indication& crc_pdu); /// Update UE with the latest CSI report for a given cell. - void handle_csi_report(const csi_report_data& csi_report) - { - set_fallback_state(false); - if (not channel_state.handle_csi_report(csi_report)) { - logger.warning("ue={} rnti={:#x}: Invalid CSI report received", ue_index, rnti()); - } - } + void handle_csi_report(const csi_report_data& csi_report); /// \brief Get the current UE cell metrics. const metrics& get_metrics() const { return ue_metrics; } @@ -142,10 +138,14 @@ class ue_cell const ue_link_adaptation_controller& link_adaptation_controller() const { return ue_mcs_calculator; } private: + /// \brief Performs link adaptation procedures such as cancelling HARQs etc. + void apply_link_adaptation_procedures(const csi_report_data& csi_report); + rnti_t crnti_; const cell_configuration& cell_cfg; ue_cell_configuration ue_cfg; sched_ue_resource_alloc_config ue_res_alloc_cfg; + scheduler_ue_expert_config expert_cfg; srslog::basic_logger& logger; /// \brief Fallback state of the UE. When in "fallback" mode, only the search spaces of cellConfigCommon are used. diff --git a/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp b/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp index 1df3e9de37..fd8c5f3233 100644 --- a/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp +++ b/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp @@ -47,10 +47,23 @@ void ue_cell_grid_allocator::add_cell(du_cell_index_t cell_index, cells.emplace(cell_index, cell_t{cell_index, &pdcch_sched, &uci_alloc, &cell_alloc}); } -bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) +void ue_cell_grid_allocator::slot_indication() +{ + dl_attempts_count = 0; + ul_attempts_count = 0; +} + +alloc_outcome ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) { srsran_assert(ues.contains(grant.user->ue_index), "Invalid UE candidate index={}", grant.user->ue_index); srsran_assert(has_cell(grant.cell_index), "Invalid UE candidate cell_index={}", grant.cell_index); + + if (dl_attempts_count++ >= expert_cfg.max_pdcch_alloc_attempts_per_slot) { + logger.debug("Stopping DL allocations. Cause: Max number of DL PDCCH allocation attempts {} reached.", + expert_cfg.max_pdcch_alloc_attempts_per_slot); + return alloc_outcome::skip_slot; + } + ue& u = ues[grant.user->ue_index]; // Verify UE carrier is active. @@ -59,7 +72,7 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) logger.warning("PDSCH allocation failed. Cause: The ue={} carrier with cell_index={} is inactive", u.ue_index, grant.cell_index); - return false; + return alloc_outcome::skip_ue; } const ue_cell_configuration& ue_cell_cfg = ue_cc->cfg(); @@ -72,11 +85,11 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) const search_space_info* ss_info = ue_cell_cfg.find_search_space(grant.ss_id); if (ss_info == nullptr) { logger.warning("Failed to allocate PDSCH. Cause: No valid SearchSpace found."); - return false; + return alloc_outcome::invalid_params; } if (ss_info->bwp->bwp_id != ue_cc->active_bwp_id()) { logger.warning("Failed to allocate PDSCH. Cause: SearchSpace not valid for active BWP."); - return false; + return alloc_outcome::invalid_params; } const search_space_configuration& ss_cfg = *ss_info->cfg; const coreset_configuration& cs_cfg = *ss_info->coreset; @@ -97,15 +110,15 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) // CRC scrambled by a RA-RNTI, a MsgB-RNTI, or a TC-RNTI on the primary cell. if (dci_type == dci_dl_rnti_config_type::tc_rnti_f1_0 and grant.ss_id != cell_cfg.dl_cfg_common.init_dl_bwp.pdcch_common.ra_search_space_id) { - logger.info("Failed to allocate PDSCH. Cause: SearchSpace not valid for re-transmission of msg4."); - return false; + logger.info("Failed to allocate PDSCH. Cause: SearchSpace not valid for re-transmission of Msg4."); + return alloc_outcome::invalid_params; } // Note: Unable to multiplex CSI and SRB0 retransmission. if (dci_type == dci_dl_rnti_config_type::tc_rnti_f1_0 and not get_res_alloc(grant.cell_index)[0].result.dl.csi_rs.empty()) { logger.info("Failed to allocate PDSCH. Cause: Multiplexing of CSI-RS and TC-RNTI retransmission is not allowed."); - return false; + return alloc_outcome::skip_ue; } const span pdsch_list = ss_info->pdsch_time_domain_list; @@ -115,13 +128,20 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) cell_slot_resource_allocator& pdcch_alloc = get_res_alloc(grant.cell_index)[0]; cell_slot_resource_allocator& pdsch_alloc = get_res_alloc(grant.cell_index)[pdsch_td_cfg.k0]; + if (pdsch_alloc.result.dl.bc.sibs.size() + pdsch_alloc.result.dl.paging_grants.size() + + pdsch_alloc.result.dl.rar_grants.size() + pdsch_alloc.result.dl.ue_grants.size() >= + expert_cfg.max_pdschs_per_slot) { + logger.info("Failed to allocate PDSCH. Cause: Max number of PDSCHs per slot {} was reached.", + expert_cfg.max_pdschs_per_slot); + return alloc_outcome::skip_slot; + } if (not cell_cfg.is_dl_enabled(pdcch_alloc.slot)) { logger.warning("Failed to allocate PDSCH in slot={}. Cause: DL is not active in the PDCCH slot", pdsch_alloc.slot); - return false; + return alloc_outcome::skip_slot; } if (not cell_cfg.is_dl_enabled(pdsch_alloc.slot)) { logger.warning("Failed to allocate PDSCH in slot={}. Cause: DL is not active in the PDSCH slot", pdsch_alloc.slot); - return false; + return alloc_outcome::invalid_params; } // Check whether PDSCH time domain resource does not overlap with CORESET. if (pdsch_td_cfg.symbols.start() < ss_cfg.get_first_symbol_index() + cs_cfg.duration) { @@ -129,7 +149,7 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) "CORESET symbols", pdsch_alloc.slot, grant.time_res_index); - return false; + return alloc_outcome::invalid_params; } // Check whether PDSCH time domain resource fits in DL symbols of the slot. if (pdsch_td_cfg.symbols.stop() > cell_cfg.get_nof_dl_symbol_per_slot(pdsch_alloc.slot)) { @@ -137,36 +157,45 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) "domain resource ={}", pdsch_alloc.slot, grant.time_res_index); - return false; + return alloc_outcome::invalid_params; } - // Verify there is space in PDSCH and PDCCH result lists for new allocations. if (pdsch_alloc.result.dl.ue_grants.full() or pdcch_alloc.result.dl.dl_pdcchs.full()) { - logger.warning("Failed to allocate PDSCH. Cause: No space available in scheduler output list"); - return false; + logger.warning("ue={} rnti={:#x}: Failed to allocate PDSCH. Cause: No space available in scheduler output list", + u.ue_index, + u.crnti); + return alloc_outcome::skip_slot; } // Verify CRBs fit in the chosen BWP. if (not ss_info->dl_crb_lims.contains(grant.crbs)) { - logger.warning( - "Failed to allocate PDSCH. Cause: CRBs={} are outside the valid limits={}.", grant.crbs, ss_info->dl_crb_lims); - return false; + logger.warning("ue={} rnti={:#x}: Failed to allocate PDSCH. Cause: CRBs={} are outside the valid limits={}.", + u.ue_index, + u.crnti, + grant.crbs, + ss_info->dl_crb_lims); + return alloc_outcome::invalid_params; } // In case of retx, ensure the number of PRBs for the grant did not change. if (not h_dl.empty() and grant.crbs.length() != h_dl.last_alloc_params().rbs.type1().length()) { - logger.warning("Failed to allocate PDSCH. Cause: Number of CRBs has to remain constant during retxs (Harq-id={}, " - "nof_prbs={}!={})", + logger.warning("ue={} rnti={:#x}: Failed to allocate PDSCH. Cause: Number of CRBs has to remain constant during " + "retxs (Harq-id={}, nof_prbs={}!={})", + u.ue_index, + u.crnti, h_dl.id, h_dl.last_alloc_params().rbs.type1().length(), grant.crbs.length()); - return false; + return alloc_outcome::invalid_params; } // Verify there is no RB collision. if (pdsch_alloc.dl_res_grid.collides(bwp_dl_cmn.generic_params.scs, pdsch_td_cfg.symbols, grant.crbs)) { - logger.warning("Failed to allocate PDSCH. Cause: No space available in scheduler RB resource grid."); - return false; + logger.warning( + "ue={} rnti={:#x}: Failed to allocate PDSCH. Cause: No space available in scheduler RB resource grid.", + u.ue_index, + u.crnti); + return alloc_outcome::invalid_params; } // Allocate PDCCH position. @@ -179,8 +208,10 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) .alloc_dl_pdcch_ue(pdcch_alloc, u.crnti, ue_cell_cfg, ss_cfg.get_id(), grant.aggr_lvl); } if (pdcch == nullptr) { - logger.info("Failed to allocate PDSCH. Cause: No space in PDCCH."); - return false; + logger.info("ue={} rnti={:#x}: Failed to allocate PDSCH. Cause: No space in PDCCH.", u.ue_index, u.crnti); + // TODO: For now, given that only one searchSpace is used, we skip the UE. In the future, we might still try + // different searchSpaces. + return alloc_outcome::skip_ue; } // Allocate UCI. UCI destination (i.e., PUCCH or PUSCH) depends on whether there exist a PUSCH grant for the UE. @@ -197,9 +228,11 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) k1 = uci.k1; pdcch->ctx.context.harq_feedback_timing = k1; } else { - logger.info("Failed to allocate PDSCH. Cause: No space in PUCCH."); + logger.info("ue={} rnti={:#x}: Failed to allocate PDSCH. Cause: No space in PUCCH.", u.ue_index, u.crnti); get_pdcch_sched(grant.cell_index).cancel_last_pdcch(pdcch_alloc); - return false; + // TODO: For now, given that only one searchSpace is used, we skip the UE. In the future, we might still try + // different searchSpaces. + return alloc_outcome::skip_ue; } pdsch_config_params pdsch_cfg; @@ -221,7 +254,16 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) // Reduce estimated MCS by 1 whenever CSI-RS is sent over a particular slot to account for the overhead of CSI-RS REs. sch_mcs_index adjusted_mcs{grant.mcs}; if (not pdsch_alloc.result.dl.csi_rs.empty()) { - adjusted_mcs = adjusted_mcs == 0 ? adjusted_mcs : adjusted_mcs - 1; + // [Implementation-defined] The max MCS values below are set empirically and should avoid the effective code rate to + // exceed 0.95 due to the overhead of CSI-RS REs. + adjusted_mcs = adjusted_mcs == 0 ? adjusted_mcs : adjusted_mcs - 1; + uint8_t max_mcs_with_csi_rs = 28; + if (pdsch_cfg.mcs_table == pdsch_mcs_table::qam64) { + max_mcs_with_csi_rs = 26U; + } else if (pdsch_cfg.mcs_table == pdsch_mcs_table::qam256) { + max_mcs_with_csi_rs = 24U; + } + adjusted_mcs = static_cast(std::min(adjusted_mcs.to_uint(), max_mcs_with_csi_rs)); } optional mcs_tbs_info; @@ -236,9 +278,10 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) // If there is not MCS-TBS info, it means no MCS exists such that the effective code rate is <= 0.95. if (not mcs_tbs_info.has_value()) { - logger.warning("Failed to allocate PDSCH. Cause: no MCS such that code rate <= 0.95."); + logger.warning( + "ue={} rnti={:#x}: Failed to allocate PDSCH. Cause: no MCS such that code rate <= 0.95.", u.ue_index, u.crnti); get_pdcch_sched(grant.cell_index).cancel_last_pdcch(pdcch_alloc); - return false; + return alloc_outcome::invalid_params; } // Mark resources as occupied in the ResourceGrid. @@ -247,15 +290,21 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) // Allocate UE DL HARQ. if (h_dl.empty()) { // It is a new tx. - // TODO: Compute total DAI when using DCI Format 1_1 if UE is configured with multiple serving cells. - h_dl.new_tx(pdsch_alloc.slot, k1, expert_cfg.max_nof_harq_retxs, uci.dai); + h_dl.new_tx(pdsch_alloc.slot, + k1, + expert_cfg.max_nof_harq_retxs, + uci.harq_bit_idx, + ue_cc->channel_state_manager().get_wideband_cqi(), + ue_cc->channel_state_manager().get_nof_dl_layers()); } else { // It is a retx. - h_dl.new_retx(pdsch_alloc.slot, k1, uci.dai); + h_dl.new_retx(pdsch_alloc.slot, k1, uci.harq_bit_idx); } // Fill DL PDCCH DCI PDU. - uint8_t rv = ue_cc->get_pdsch_rv(h_dl); + // Number of possible Downlink Assignment Indexes {0, ..., 3} as per TS38.213 Section 9.1.3. + static constexpr unsigned DAI_MOD = 4U; + uint8_t rv = ue_cc->get_pdsch_rv(h_dl); switch (dci_type) { case dci_dl_rnti_config_type::tc_rnti_f1_0: build_dci_f1_0_tc_rnti(pdcch->dci, @@ -276,7 +325,7 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) grant.time_res_index, k1, uci.pucch_grant.pucch_res_indicator, - uci.dai, + uci.harq_bit_idx % DAI_MOD, mcs_tbs_info.value().mcs, rv, h_dl); @@ -289,7 +338,7 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) grant.time_res_index, k1, uci.pucch_grant.pucch_res_indicator, - uci.dai, + uci.harq_bit_idx % DAI_MOD, mcs_tbs_info.value().mcs, rv, h_dl, @@ -354,15 +403,21 @@ bool ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& grant) u.build_dl_transport_block_info(msg.tb_list.emplace_back(), msg.pdsch_cfg.codewords[0].tb_size_bytes); } - return true; + return alloc_outcome::success; } -bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) +alloc_outcome ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) { srsran_assert(ues.contains(grant.user->ue_index), "Invalid UE candidate index={}", grant.user->ue_index); srsran_assert(has_cell(grant.cell_index), "Invalid UE candidate cell_index={}", grant.cell_index); constexpr static unsigned pdcch_delay_in_slots = 0; + if (ul_attempts_count++ >= expert_cfg.max_pdcch_alloc_attempts_per_slot) { + logger.debug("Stopping UL allocations. Cause: Max number of UL PDCCH allocation attempts {} reached.", + expert_cfg.max_pdcch_alloc_attempts_per_slot); + return alloc_outcome::skip_slot; + } + ue& u = ues[grant.user->ue_index]; // Verify UE carrier is active. @@ -371,7 +426,7 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) logger.warning("PUSCH allocation failed. Cause: The ue={} carrier with cell_index={} is inactive", u.ue_index, grant.cell_index); - return false; + return alloc_outcome::skip_ue; } const ue_cell_configuration& ue_cell_cfg = ue_cc->cfg(); @@ -382,13 +437,13 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) const search_space_info* ss_info = ue_cell_cfg.find_search_space(grant.ss_id); if (ss_info == nullptr) { logger.warning("Failed to allocate PUSCH. Cause: No valid SearchSpace found."); - return false; + return alloc_outcome::invalid_params; } if (ss_info->bwp->bwp_id != ue_cc->active_bwp_id()) { logger.warning("Failed to allocate PUSCH. Cause: Chosen SearchSpace {} does not belong to the active BWP {}", grant.ss_id, ue_cc->active_bwp_id()); - return false; + return alloc_outcome::invalid_params; } const search_space_configuration& ss_cfg = *ss_info->cfg; const bwp_uplink_common& bwp_ul_cmn = *ss_info->bwp->ul_common; @@ -407,7 +462,7 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) if (dci_type == dci_ul_rnti_config_type::tc_rnti_f0_0 and grant.ss_id != cell_cfg.dl_cfg_common.init_dl_bwp.pdcch_common.ra_search_space_id) { logger.info("Failed to allocate PUSCH. Cause: SearchSpace not valid for re-transmission."); - return false; + return alloc_outcome::invalid_params; } // In case of re-transmission DCI format must remain same and therefore its necessary to find the SS which support @@ -418,7 +473,7 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) u.crnti, dci_ul_rnti_config_format(h_ul.last_tx_params().dci_cfg_type), grant.ss_id); - return false; + return alloc_outcome::invalid_params; } // Fetch PDCCH and PDSCH resource grid allocators. @@ -429,14 +484,19 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) logger.warning("rnti={:#x} Failed to allocate PUSCH in slot={}. Cause: DL is not active in the PDCCH slot", u.crnti, pusch_alloc.slot); - return false; + return alloc_outcome::skip_slot; } if (not cell_cfg.is_ul_enabled(pusch_alloc.slot)) { logger.warning("rnti={:#x} Failed to allocate PUSCH in slot={}. Cause: UL is not active in the PUSCH slot (k2={})", u.crnti, pusch_alloc.slot, pusch_td_cfg.k2); - return false; + return alloc_outcome::invalid_params; + } + if (pusch_alloc.result.ul.puschs.size() >= expert_cfg.max_puschs_per_slot) { + logger.info("Failed to allocate PUSCH. Cause: Max number of PUSCHs per slot {} was reached.", + expert_cfg.max_puschs_per_slot); + return alloc_outcome::skip_slot; } // We skip allocation of PUSCH in the slots with the CSI reporting over PUCCH. @@ -445,15 +505,15 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) logger.debug("rnti={:#x} Allocation of PUSCH in slot={} skipped. Cause: this slot is for CSI reporting over PUCCH", u.crnti, pusch_alloc.slot); - return false; + return alloc_outcome::skip_slot; } // Verify there is space in PUSCH and PDCCH result lists for new allocations. - if (pusch_alloc.result.ul.puschs.full() or pdcch_alloc.result.dl.dl_pdcchs.full()) { + if (pusch_alloc.result.ul.puschs.full() or pdcch_alloc.result.dl.ul_pdcchs.full()) { logger.warning("rnti={:#x} Failed to allocate PUSCH in slot={}. Cause: No space available in scheduler output list", u.crnti, pusch_alloc.slot); - return false; + return alloc_outcome::skip_slot; } // [Implementation-defined] We skip allocation of PUSCH if there is already a PUCCH grant scheduled over the same slot @@ -470,7 +530,7 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) "PUCCH grants scheduled", u.crnti, pusch_alloc.slot); - return false; + return alloc_outcome::skip_ue; } } @@ -480,7 +540,7 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) u.crnti, grant.crbs, ss_info->ul_crb_lims); - return false; + return alloc_outcome::invalid_params; } // In case of retx, ensure the number of PRBs for the grant did not change. @@ -492,14 +552,14 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) h_ul.id, h_ul.last_tx_params().rbs.type1().length(), grant.crbs.length()); - return false; + return alloc_outcome::invalid_params; } // Verify there is no RB collision. if (pusch_alloc.ul_res_grid.collides(scs, pusch_td_cfg.symbols, grant.crbs)) { logger.warning("rnti={:#x} Failed to allocate PUSCH. Cause: No space available in scheduler RB resource grid.", u.crnti); - return false; + return alloc_outcome::invalid_params; } // Allocate PDCCH position. @@ -508,7 +568,7 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) .alloc_ul_pdcch_ue(pdcch_alloc, u.crnti, ue_cell_cfg, ss_cfg.get_id(), grant.aggr_lvl); if (pdcch == nullptr) { logger.info("rnti={:#x}: Failed to allocate PUSCH. Cause: No space in PDCCH.", u.crnti); - return false; + return alloc_outcome::skip_ue; } // Fetch PUSCH parameters based on type of transmission. @@ -565,7 +625,7 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) .ul_config->init_ul_bwp.pusch_cfg->pusch_mapping_type_a_dmrs.value() .additional_positions)); get_pdcch_sched(grant.cell_index).cancel_last_pdcch(pdcch_alloc); - return false; + return alloc_outcome::invalid_params; } // Mark resources as occupied in the ResourceGrid. @@ -691,5 +751,5 @@ bool ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& grant) // In case there is a SR pending. Reset it. u.reset_sr_indication(); - return true; + return alloc_outcome::success; } diff --git a/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.h b/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.h index 0a1fcd8beb..93ef3c478c 100644 --- a/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.h +++ b/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.h @@ -48,9 +48,11 @@ class ue_cell_grid_allocator : public ue_pdsch_allocator, public ue_pusch_alloca size_t nof_cells() const { return cells.size(); } - bool allocate_dl_grant(const ue_pdsch_grant& grant) override; + void slot_indication(); - bool allocate_ul_grant(const ue_pusch_grant& grant) override; + alloc_outcome allocate_dl_grant(const ue_pdsch_grant& grant) override; + + alloc_outcome allocate_ul_grant(const ue_pusch_grant& grant) override; private: struct cell_t { @@ -78,6 +80,9 @@ class ue_cell_grid_allocator : public ue_pdsch_allocator, public ue_pusch_alloca srslog::basic_logger& logger; slotted_array cells; + + // Number of allocation attempts for DL and UL in the given slot. + unsigned dl_attempts_count = 0, ul_attempts_count = 0; }; } // namespace srsran diff --git a/lib/scheduler/ue_scheduling/ue_configuration.cpp b/lib/scheduler/ue_scheduling/ue_configuration.cpp index 25f10a8bab..377be3ca99 100644 --- a/lib/scheduler/ue_scheduling/ue_configuration.cpp +++ b/lib/scheduler/ue_scheduling/ue_configuration.cpp @@ -21,6 +21,7 @@ */ #include "ue_configuration.h" +#include "../support/pdcch/pdcch_mapping.h" #include "../support/pdsch/pdsch_default_time_allocation.h" #include "../support/pdsch/pdsch_resource_allocation.h" #include "../support/pusch/pusch_default_time_allocation.h" @@ -43,10 +44,31 @@ span search_space_info::get_k1_candidates() const } void search_space_info::update_pdcch_candidates( - const std::vector>& candidates) + const std::vector>& candidates, + pci_t pci) { srsran_assert(candidates.size() > 0, "The SearchSpace doesn't have any candidates"); ss_pdcch_candidates = candidates; + + crbs_of_candidates.resize(ss_pdcch_candidates.size()); + for (unsigned sl = 0; sl < ss_pdcch_candidates.size(); sl++) { + for (unsigned lidx = 0; lidx < ss_pdcch_candidates[sl].size(); lidx++) { + const aggregation_level aggr_lvl = aggregation_index_to_level(lidx); + crbs_of_candidates[sl][lidx].resize(ss_pdcch_candidates[sl][lidx].size()); + for (unsigned candidate_idx = 0; candidate_idx != ss_pdcch_candidates[sl][lidx].size(); ++candidate_idx) { + uint8_t ncce = ss_pdcch_candidates[sl][lidx][candidate_idx]; + auto& crb_list = crbs_of_candidates[sl][lidx][candidate_idx]; + + // Get PRBs for each candidate. + crb_list = pdcch_helper::cce_to_prb_mapping(bwp->dl_common->generic_params, *coreset, pci, aggr_lvl, ncce); + + // Convert PRBs to CRBs. + for (uint16_t& prb_idx : crb_list) { + prb_idx = prb_to_crb(bwp->dl_common->generic_params.crbs, prb_idx); + } + } + } + } } /// \brief Determines whether the given DCI format is monitored in UE specific SS or not. @@ -413,6 +435,9 @@ static void apply_pdcch_candidate_monitoring_limits(frame_pdcch_candidate_list& { // TS 38.213, Table 10.1-3 - Maximum number of non-overlapped CCEs. const static std::array max_non_overlapped_cces_per_slot = {56, 56, 48, 32}; + // Maximum nof. CCEs in a CORESET = maximum PDCCH frequency resources * maximum CORESET duration. + const static unsigned maximum_nof_cces = + pdcch_constants::MAX_NOF_FREQ_RESOURCES * pdcch_constants::MAX_CORESET_DURATION; const unsigned max_pdcch_candidates = max_nof_monitored_pdcch_candidates(bwp.dl_common->generic_params.scs); const unsigned max_non_overlapped_cces = @@ -433,24 +458,49 @@ static void apply_pdcch_candidate_monitoring_limits(frame_pdcch_candidate_list& } } - // "CCES for PDCCH candidates are non-overlapped if they correspond to: + // Limit number of non-overlapped CCEs monitored per slot. + // CCES for PDCCH candidates are non-overlapped if they correspond to: // - different CORESET indexes; or // - different first symbols for the reception of the respective PDCCH candidates. - // TODO: Account for second condition. - unsigned cce_count = 0; - std::array non_overlapped_cce_flag{false}; - for (const search_space_info* ss : bwp.search_spaces) { + // TODO: Account for multiple SSB beams. + std::array, NOF_OFDM_SYM_PER_SLOT_NORMAL_CP>, MAX_NOF_CORESETS_PER_BWP> + cce_bitmap_per_coreset_per_first_pdcch_symb; + for (unsigned cs_idx = 0; cs_idx < MAX_NOF_CORESETS_PER_BWP; ++cs_idx) { + std::fill(cce_bitmap_per_coreset_per_first_pdcch_symb[cs_idx].begin(), + cce_bitmap_per_coreset_per_first_pdcch_symb[cs_idx].end(), + bounded_bitset(maximum_nof_cces)); + } + auto get_cce_monitored_sum = [&cce_bitmap_per_coreset_per_first_pdcch_symb]() { + unsigned monitored_cces = 0; + for (unsigned cs_idx = 0; cs_idx < MAX_NOF_CORESETS_PER_BWP; ++cs_idx) { + for (unsigned symb = 0; symb < NOF_OFDM_SYM_PER_SLOT_NORMAL_CP; ++symb) { + monitored_cces += cce_bitmap_per_coreset_per_first_pdcch_symb[cs_idx][symb].count(); + } + } + return monitored_cces; + }; + for (unsigned ss1_idx = 0; ss1_idx < bwp.search_spaces.size(); ++ss1_idx) { + const search_space_info* ss1 = bwp.search_spaces[static_cast(ss1_idx)]; for (unsigned i = 0; i != NOF_AGGREGATION_LEVELS; ++i) { - pdcch_candidate_list& ss_candidates = candidates[ss->cfg->get_id()][slot_index][i]; - if (not non_overlapped_cce_flag[ss->coreset->id]) { - non_overlapped_cce_flag[ss->coreset->id] = true; - - unsigned ss_nof_cces = to_nof_cces(aggregation_index_to_level(i)) * ss_candidates.size(); - while (max_non_overlapped_cces < cce_count + ss_nof_cces) { - ss_candidates.pop_back(); - ss_nof_cces -= to_nof_cces(aggregation_index_to_level(i)); + pdcch_candidate_list& ss_candidates = candidates[ss1->cfg->get_id()][slot_index][i]; + for (auto* it2 = ss_candidates.begin(); it2 != ss_candidates.end();) { + // Take backup of monitored CCEs at current CORESET and first PDCCH monitoring occasion symbol of + // corresponding SearchSpace. This is used to reset the monitored CCEs back to original state if max. nof. + // non-overlapped CCEs monitored limit is exceeded. + const bounded_bitset cce_monitored_backup( + cce_bitmap_per_coreset_per_first_pdcch_symb[ss1->coreset->id][ss1->cfg->get_first_symbol_index()]); + // Set the CCEs monitored. + cce_bitmap_per_coreset_per_first_pdcch_symb[ss1->coreset->id][ss1->cfg->get_first_symbol_index()].fill( + *it2, *it2 + to_nof_cces(static_cast(i)), true); + if (get_cce_monitored_sum() > max_non_overlapped_cces) { + // Case: max. nof. non-overlapped CCEs exceeded. + it2 = ss_candidates.erase(it2); + // Reset the monitored CCEs. + cce_bitmap_per_coreset_per_first_pdcch_symb[ss1->coreset->id][ss1->cfg->get_first_symbol_index()] = + cce_monitored_backup; + } else { + ++it2; } - cce_count += ss_nof_cces; } } } @@ -458,7 +508,7 @@ static void apply_pdcch_candidate_monitoring_limits(frame_pdcch_candidate_list& } /// \brief Compute the list of PDCCH candidates being monitored for each SearchSpace for a given slot index. -static void generate_crnti_monitored_pdcch_candidates(bwp_info& bwp_cfg, rnti_t crnti) +static void generate_crnti_monitored_pdcch_candidates(bwp_info& bwp_cfg, rnti_t crnti, pci_t pci) { const unsigned slots_per_frame = NOF_SUBFRAMES_PER_FRAME * get_nof_slots_per_subframe(bwp_cfg.dl_common->generic_params.scs); @@ -477,7 +527,7 @@ static void generate_crnti_monitored_pdcch_candidates(bwp_info& bwp_cfg, rnti_t frame_pdcch_candidate_list candidates; // Compute PDCCH candidates for each Search Space, without accounting for special monitoring rules. - for (const search_space_info* ss : bwp_cfg.search_spaces) { + for (search_space_info* ss : bwp_cfg.search_spaces) { candidates.emplace(ss->cfg->get_id()); auto& ss_candidates = candidates[ss->cfg->get_id()]; ss_candidates.resize(max_slot_periodicity); @@ -518,7 +568,7 @@ static void generate_crnti_monitored_pdcch_candidates(bwp_info& bwp_cfg, rnti_t // Save resulting candidates for this slot. for (search_space_info* ss : bwp_cfg.search_spaces) { - ss->update_pdcch_candidates(candidates[ss->cfg->get_id()]); + ss->update_pdcch_candidates(candidates[ss->cfg->get_id()], pci); } } @@ -575,7 +625,7 @@ void ue_cell_configuration::reconfigure(const serving_cell_config& cell_cfg_ded_ // Generate PDCCH candidates. for (bwp_info& bwp : bwp_table) { if (bwp.dl_common != nullptr) { - generate_crnti_monitored_pdcch_candidates(bwp, crnti); + generate_crnti_monitored_pdcch_candidates(bwp, crnti, cell_cfg_common.pci); } } } diff --git a/lib/scheduler/ue_scheduling/ue_configuration.h b/lib/scheduler/ue_scheduling/ue_configuration.h index e17c8ae430..24b6b9811f 100644 --- a/lib/scheduler/ue_scheduling/ue_configuration.h +++ b/lib/scheduler/ue_scheduling/ue_configuration.h @@ -26,6 +26,7 @@ #include "../support/pdcch/search_space_helper.h" #include "srsran/adt/static_vector.h" #include "srsran/ran/du_types.h" +#include "srsran/ran/pdcch/cce_to_prb_mapping.h" #include "srsran/ran/pdcch/pdcch_candidates.h" #include "srsran/scheduler/config/bwp_configuration.h" @@ -45,6 +46,9 @@ struct bwp_info { slotted_id_table search_spaces; }; +/// List of CRBs for a given PDCCH candidate. +using crb_index_list = static_vector; + /// \brief Grouping of common and UE-dedicated information associated with a given search space. struct search_space_info { const search_space_configuration* cfg = nullptr; @@ -74,8 +78,15 @@ struct search_space_info { return ss_pdcch_candidates[pdcch_slot.to_uint() % ss_pdcch_candidates.size()][to_aggregation_level_index(aggr_lvl)]; } + /// \brief Retrieve all the CRBs for a given aggregation level and searchSpace candidate. + span get_crb_list_of_pdcch_candidates(aggregation_level aggr_lvl, slot_point pdcch_slot) const + { + return crbs_of_candidates[pdcch_slot.to_uint() % crbs_of_candidates.size()][to_aggregation_level_index(aggr_lvl)]; + } + /// \brief Assigns computed PDCCH candidates to a SearchSpace. - void update_pdcch_candidates(const std::vector>& candidates); + void update_pdcch_candidates(const std::vector>& candidates, + pci_t pci); private: // PDCCH candidates of the SearchSpace for different slot offsets and aggregation levels. Indexed by @@ -83,6 +94,9 @@ struct search_space_info { // We need to keep separate lists for different slot offsets because PDCCH candidates change with the slot index, // may have a monitoring periodicity above 1 slot, and may be affected by the candidates of other search spaces. std::vector> ss_pdcch_candidates; + + // List of CRBs used by each PDCCH candidate. + std::vector, NOF_AGGREGATION_LEVELS>> crbs_of_candidates; }; /// UE-dedicated configuration for a given cell. diff --git a/lib/scheduler/ue_scheduling/ue_event_manager.cpp b/lib/scheduler/ue_scheduling/ue_event_manager.cpp index 2ae2f053a8..8b2ea2d852 100644 --- a/lib/scheduler/ue_scheduling/ue_event_manager.cpp +++ b/lib/scheduler/ue_scheduling/ue_event_manager.cpp @@ -145,7 +145,7 @@ void ue_event_manager::handle_ul_bsr_indication(const ul_bsr_indication_message& void ue_event_manager::handle_ul_phr_indication(const ul_phr_indication_message& phr_ind) { - for (const auto& cell_phr : phr_ind.phr.get_phr()) { + for (const cell_ph_report& cell_phr : phr_ind.phr.get_phr()) { srsran_sanity_check(cell_exists(cell_phr.serv_cell_id), "Invalid serving cell index={}", cell_phr.serv_cell_id); cell_specific_events[cell_phr.serv_cell_id].emplace( @@ -204,7 +204,8 @@ void ue_event_manager::handle_crc_indication(const ul_crc_indication& crc_ind) void ue_event_manager::handle_harq_ind(ue_cell& ue_cc, slot_point uci_sl, span harq_bits, - bool is_pucch_f1) + bool is_pucch_f1, + optional pucch_snr) { for (unsigned harq_idx = 0; harq_idx != harq_bits.size(); ++harq_idx) { mac_harq_ack_report_status ack_value = harq_bits[harq_idx]; @@ -215,7 +216,7 @@ void ue_event_manager::handle_harq_ind(ue_cell& ue } // Update UE state. - const dl_harq_process* h_dl = ue_cc.handle_dl_ack_info(uci_sl, ack_value, harq_idx); + const dl_harq_process* h_dl = ue_cc.handle_dl_ack_info(uci_sl, ack_value, harq_idx, pucch_snr); if (h_dl != nullptr) { // HARQ was found. const units::bytes tbs{h_dl->last_alloc_params().tb[0]->tbs_bytes}; @@ -259,7 +260,7 @@ void ue_event_manager::handle_uci_indication(const uci_indication& ind) // Process DL HARQ ACKs. if (not pdu.harqs.empty()) { - handle_harq_ind(ue_cc, uci_sl, pdu.harqs, true); + handle_harq_ind(ue_cc, uci_sl, pdu.harqs, true, pdu.ul_sinr); } // Process SRs. @@ -286,7 +287,7 @@ void ue_event_manager::handle_uci_indication(const uci_indication& ind) // Process DL HARQ ACKs. if (not pdu.harqs.empty()) { - handle_harq_ind(ue_cc, uci_sl, pdu.harqs, false); + handle_harq_ind(ue_cc, uci_sl, pdu.harqs, false, nullopt); } // Process CSI. @@ -299,7 +300,7 @@ void ue_event_manager::handle_uci_indication(const uci_indication& ind) // Process DL HARQ ACKs. if (not pdu.harqs.empty()) { - handle_harq_ind(ue_cc, uci_sl, pdu.harqs, false); + handle_harq_ind(ue_cc, uci_sl, pdu.harqs, false, pdu.ul_sinr); } // Process SRs. diff --git a/lib/scheduler/ue_scheduling/ue_event_manager.h b/lib/scheduler/ue_scheduling/ue_event_manager.h index b7776fe9f5..3e41a29987 100644 --- a/lib/scheduler/ue_scheduling/ue_event_manager.h +++ b/lib/scheduler/ue_scheduling/ue_event_manager.h @@ -106,7 +106,8 @@ class ue_event_manager final : public scheduler_ue_configurator, void handle_harq_ind(ue_cell& ue_cc, slot_point uci_sl, span harq_bits, - bool is_pucch_f1); + bool is_pucch_f1, + optional pucch_snr); void handle_csi(ue_cell& ue_cc, const csi_report_data& csi_rep); const scheduler_ue_expert_config& expert_cfg; diff --git a/lib/scheduler/ue_scheduling/ue_pdsch_param_candidate_searcher.h b/lib/scheduler/ue_scheduling/ue_pdsch_param_candidate_searcher.h index 66855428e4..49d1b8d5a8 100644 --- a/lib/scheduler/ue_scheduling/ue_pdsch_param_candidate_searcher.h +++ b/lib/scheduler/ue_scheduling/ue_pdsch_param_candidate_searcher.h @@ -208,7 +208,6 @@ class ue_pdsch_param_candidate_searcher // Update alloc_params list. ss_candidate_list = ue_cc.get_active_dl_search_spaces(pdcch_slot, preferred_rnti_type); current_rnti_type = preferred_rnti_type; - srsran_assert(not ss_candidate_list.empty(), "No searchSpace candidates for rnti type={}", preferred_rnti_type); } // Check if a candidate has valid parameters for an allocation. diff --git a/lib/scheduler/ue_scheduling/ue_repository.cpp b/lib/scheduler/ue_scheduling/ue_repository.cpp index ca349d2713..526d79a7a1 100644 --- a/lib/scheduler/ue_scheduling/ue_repository.cpp +++ b/lib/scheduler/ue_scheduling/ue_repository.cpp @@ -27,6 +27,7 @@ using namespace srsran; ue_repository::ue_repository(sched_configuration_notifier& mac_notif_) : mac_notif(mac_notif_), logger(srslog::fetch_basic_logger("SCHED")) { + rnti_to_ue_index_lookup.reserve(MAX_NOF_DU_UES); } /// \brief This function checks whether it is safe to remove a UE. Currently we verify that: @@ -53,6 +54,16 @@ static bool is_ue_ready_for_removal(ue& u) return true; } +// Helper function to search in lookup. +static auto search_rnti(const std::vector>& rnti_to_ue_index, rnti_t rnti) +{ + auto it = + std::lower_bound(rnti_to_ue_index.begin(), rnti_to_ue_index.end(), rnti, [](const auto& lhs, rnti_t rnti_v) { + return lhs.first < rnti_v; + }); + return it != rnti_to_ue_index.end() and it->first == rnti ? it : rnti_to_ue_index.end(); +} + void ue_repository::slot_indication(slot_point sl_tx) { for (du_ue_index_t& ue_index : ues_to_rem) { @@ -76,6 +87,14 @@ void ue_repository::slot_indication(slot_point sl_tx) // Notify MAC of the successful UE removal. mac_notif.on_ue_delete_response(ue_index); + // Remove UE from lookup. + auto it = search_rnti(rnti_to_ue_index_lookup, u.crnti); + if (it != rnti_to_ue_index_lookup.end()) { + rnti_to_ue_index_lookup.erase(it); + } else { + logger.warning("UE with c-rnti={:#x} not found in RNTI -> UE index lookup.", u.crnti); + } + // Remove UE from the repository. ues.erase(ue_index); @@ -97,8 +116,14 @@ void ue_repository::slot_indication(slot_point sl_tx) void ue_repository::add_ue(std::unique_ptr u) { + // Add UE in repository. du_ue_index_t ue_index = u->ue_index; + rnti_t rnti = u->crnti; ues.insert(ue_index, std::move(u)); + + // Update RNTI -> UE index lookup. + rnti_to_ue_index_lookup.emplace_back(rnti, ue_index); + std::sort(rnti_to_ue_index_lookup.begin(), rnti_to_ue_index_lookup.end()); } void ue_repository::schedule_ue_rem(du_ue_index_t ue_index) @@ -111,3 +136,15 @@ void ue_repository::schedule_ue_rem(du_ue_index_t ue_index) ues_to_rem.push(ue_index); } } + +ue* ue_repository::find_by_rnti(rnti_t rnti) +{ + auto it = search_rnti(rnti_to_ue_index_lookup, rnti); + return it != rnti_to_ue_index_lookup.end() ? ues[it->second].get() : nullptr; +} + +const ue* ue_repository::find_by_rnti(rnti_t rnti) const +{ + auto it = search_rnti(rnti_to_ue_index_lookup, rnti); + return it != rnti_to_ue_index_lookup.end() ? ues[it->second].get() : nullptr; +} diff --git a/lib/scheduler/ue_scheduling/ue_repository.h b/lib/scheduler/ue_scheduling/ue_repository.h index 58c121cbd5..455b96d63f 100644 --- a/lib/scheduler/ue_scheduling/ue_repository.h +++ b/lib/scheduler/ue_scheduling/ue_repository.h @@ -48,6 +48,10 @@ class ue_repository ue& operator[](du_ue_index_t ue_index) { return *ues[ue_index]; } const ue& operator[](du_ue_index_t ue_index) const { return *ues[ue_index]; } + /// \brief Search UE context based on TC-RNTI/C-RNTI. + ue* find_by_rnti(rnti_t rnti); + const ue* find_by_rnti(rnti_t rnti) const; + /// \brief Add new UE in the UE repository. void add_ue(std::unique_ptr u); @@ -72,10 +76,13 @@ class ue_repository sched_configuration_notifier& mac_notif; srslog::basic_logger& logger; - /// Repository of UEs. + // Repository of UEs. ue_list ues; - /// Queue of UEs marked for later removal. + // Mapping of RNTIs to UE indexes. + std::vector> rnti_to_ue_index_lookup; + + // Queue of UEs marked for later removal. ring_buffer ues_to_rem{MAX_NOF_DU_UES}; }; diff --git a/lib/scheduler/ue_scheduling/ue_scheduler_impl.cpp b/lib/scheduler/ue_scheduling/ue_scheduler_impl.cpp index a551cc2cc8..5f76ddeb28 100644 --- a/lib/scheduler/ue_scheduling/ue_scheduler_impl.cpp +++ b/lib/scheduler/ue_scheduling/ue_scheduler_impl.cpp @@ -33,7 +33,8 @@ ue_scheduler_impl::ue_scheduler_impl(const scheduler_ue_expert_config& expert_cf sched_strategy(create_scheduler_strategy(scheduler_strategy_params{"time_rr", &srslog::fetch_basic_logger("SCHED")})), ue_db(mac_notif), ue_alloc(expert_cfg, ue_db, srslog::fetch_basic_logger("SCHED")), - event_mng(expert_cfg, ue_db, mac_notif, metric_handler, sched_ev_logger) + event_mng(expert_cfg, ue_db, mac_notif, metric_handler, sched_ev_logger), + logger(srslog::fetch_basic_logger("SCHED")) { } @@ -63,11 +64,55 @@ void ue_scheduler_impl::run_sched_strategy(slot_point slot_tx) // UCI in PUSCH and UE sending 4 HARQ ACK bits (DAI = 3). // Example: K1==K2=4 and PUSCH is allocated before PDSCH. if (expert_cfg.enable_csi_rs_pdsch_multiplexing or (*cells[0]->cell_res_alloc)[0].result.dl.csi_rs.empty()) { - sched_strategy->dl_sched(ue_alloc, ue_res_grid_view, ue_db, true); - sched_strategy->dl_sched(ue_alloc, ue_res_grid_view, ue_db, false); + sched_strategy->dl_sched(ue_alloc, ue_res_grid_view, ue_db); + } + sched_strategy->ul_sched(ue_alloc, ue_res_grid_view, ue_db); +} + +void ue_scheduler_impl::update_harq_pucch_counter(cell_resource_allocator& cell_alloc) +{ + // We need to update the PUCCH counter after the SR/CSI scheduler because the allocation of CSI/SR can add/remove + // PUCCH grants. + const unsigned HARQ_SLOT_DELAY = 0; + const auto& slot_alloc = cell_alloc[HARQ_SLOT_DELAY]; + + // Spans through the PUCCH grant list and update the HARQ-ACK PUCCH grant counter for the corresponding RNTI and HARQ + // process id. + for (const auto& pucch : slot_alloc.result.ul.pucchs) { + if ((pucch.format == pucch_format::FORMAT_1 and pucch.format_1.harq_ack_nof_bits > 0) or + (pucch.format == pucch_format::FORMAT_2 and pucch.format_2.harq_ack_nof_bits > 0)) { + ue* user = ue_db.find_by_rnti(pucch.crnti); + // This is to handle the case of a UE that gets removed after the PUCCH gets allocated and before this PUCCH is + // expected to be sent. + if (user == nullptr) { + logger.warning( + "rnti={:#x}: No user with such RNTI found in the ue scheduler database. Skipping PUCCH grant counter", + pucch.crnti, + slot_alloc.slot); + continue; + } + srsran_assert(pucch.format == pucch_format::FORMAT_1 or pucch.format == pucch_format::FORMAT_2, + "rnti={:#x}: Only PUCCH format 1 and format 2 are supported", + pucch.crnti); + const unsigned nof_harqs_per_rnti_per_slot = + pucch.format == pucch_format::FORMAT_1 ? pucch.format_1.harq_ack_nof_bits : pucch.format_2.harq_ack_nof_bits; + // Each PUCCH grants can potentially carry ACKs for different HARQ processes (as many as the harq_ack_nof_bits) + // expecting to be acknowledged on the same slot. + for (unsigned harq_bit_idx = 0; harq_bit_idx != nof_harqs_per_rnti_per_slot; ++harq_bit_idx) { + dl_harq_process* h_dl = user->get_pcell().harqs.find_dl_harq_waiting_ack_slot(slot_alloc.slot, harq_bit_idx); + if (h_dl == nullptr) { + logger.warning( + "ue={} rnti={:#x}: No DL HARQ process with state waiting-for-ack found at slot={} for harq-bit-index", + user->ue_index, + user->crnti, + slot_alloc.slot, + harq_bit_idx); + continue; + }; + h_dl->increment_pucch_counter(); + } + } } - sched_strategy->ul_sched(ue_alloc, ue_res_grid_view, ue_db, true); - sched_strategy->ul_sched(ue_alloc, ue_res_grid_view, ue_db, false); } void ue_scheduler_impl::run_slot(slot_point slot_tx, du_cell_index_t cell_index) @@ -75,6 +120,9 @@ void ue_scheduler_impl::run_slot(slot_point slot_tx, du_cell_index_t cell_index) // Process any pending events that are directed at UEs. event_mng.run(slot_tx, cell_index); + // Mark the start of a new slot in the UE grid allocator. + ue_alloc.slot_indication(); + // Run cell-specific SRB0 scheduler. cells[cell_index]->srb0_sched.run_slot(*cells[cell_index]->cell_res_alloc); @@ -83,4 +131,8 @@ void ue_scheduler_impl::run_slot(slot_point slot_tx, du_cell_index_t cell_index) // Schedule UCI as the last step. cells[cell_index]->uci_sched.run_slot(*cells[cell_index]->cell_res_alloc, slot_tx); + + // We need to update the PUCCH counter after the UCI scheduler because the allocation of CSI/SR can add/remove + // PUCCH grants. + update_harq_pucch_counter(*cells[cell_index]->cell_res_alloc); } diff --git a/lib/scheduler/ue_scheduling/ue_scheduler_impl.h b/lib/scheduler/ue_scheduling/ue_scheduler_impl.h index 00dbb32b00..a221e627a8 100644 --- a/lib/scheduler/ue_scheduling/ue_scheduler_impl.h +++ b/lib/scheduler/ue_scheduling/ue_scheduler_impl.h @@ -64,6 +64,9 @@ class ue_scheduler_impl final : public ue_scheduler private: void run_sched_strategy(slot_point sl_tx); + /// Counts the number of PUCCH grants that are allocated for a given user at a specific slot. + void update_harq_pucch_counter(cell_resource_allocator& cell_alloc); + struct cell { cell_resource_allocator* cell_res_alloc; @@ -85,7 +88,7 @@ class ue_scheduler_impl final : public ue_scheduler std::array, MAX_NOF_DU_CELLS> cells; - /// Scheduling Strategy + /// Scheduling Strategy. ue_resource_grid_view ue_res_grid_view; std::unique_ptr sched_strategy; @@ -100,6 +103,8 @@ class ue_scheduler_impl final : public ue_scheduler /// Mutex used to lock carriers for joint carrier scheduling. slot_sync_point sync_point; + + srslog::basic_logger& logger; }; } // namespace srsran diff --git a/lib/scheduler/ue_scheduling/ue_srb0_scheduler.cpp b/lib/scheduler/ue_scheduling/ue_srb0_scheduler.cpp index 92dae2612a..3d0a0244f0 100644 --- a/lib/scheduler/ue_scheduling/ue_srb0_scheduler.cpp +++ b/lib/scheduler/ue_scheduling/ue_srb0_scheduler.cpp @@ -258,7 +258,12 @@ void ue_srb0_scheduler::fill_srb0_grant(ue& u, { static constexpr uint8_t srb0_dai = 0; // Allocate DL HARQ. - h_dl.new_tx(pdsch_slot, k1, expert_cfg.max_nof_harq_retxs, srb0_dai); + h_dl.new_tx(pdsch_slot, + k1, + expert_cfg.max_nof_harq_retxs, + srb0_dai, + u.get_pcell().channel_state_manager().get_wideband_cqi(), + u.get_pcell().channel_state_manager().get_nof_dl_layers()); // Fill DL PDCCH DCI. static const uint8_t msg4_rv = 0; diff --git a/lib/scheduler/ue_scheduling/ul_logical_channel_manager.h b/lib/scheduler/ue_scheduling/ul_logical_channel_manager.h index e9064234fb..0cf7e31630 100644 --- a/lib/scheduler/ue_scheduling/ul_logical_channel_manager.h +++ b/lib/scheduler/ue_scheduling/ul_logical_channel_manager.h @@ -47,6 +47,12 @@ class ul_logical_channel_manager /// \brief Verifies if logical channel group is activated for UL. bool is_active(lcg_id_t lcg_id) const { return groups[lcg_id].active; } + /// \brief Verifies if at least one logical channel group is active. + bool is_active() const + { + return std::any_of(groups.begin(), groups.end(), [](const auto& g) { return g.active; }); + } + /// \brief Checks whether a logical channel has pending data. bool has_pending_bytes() const { @@ -86,6 +92,10 @@ class ul_logical_channel_manager /// \brief Indicate that the UE requested an UL grant. void handle_sr_indication() { + if (not is_active()) { + // Ignore SR indication if the UL has been deactivated. + return; + } sr_pending.store(true, std::memory_order::memory_order_relaxed); // TODO: handle SR indication content. } diff --git a/lib/srsvec/bit.cpp b/lib/srsvec/bit.cpp index 9117c8b40a..4307b4b0a0 100644 --- a/lib/srsvec/bit.cpp +++ b/lib/srsvec/bit.cpp @@ -26,9 +26,9 @@ #include "srsran/support/math_utils.h" #include "srsran/support/srsran_assert.h" -#ifdef HAVE_SSE +#ifdef __x86_64__ #include -#endif // HAVE_SSE +#endif // __x86_64__ using namespace srsran; using namespace srsvec; @@ -39,49 +39,30 @@ template void unpack_8bit(span unpacked, InType value) { srsran_assert(unpacked.size() == 8, "The amount of data to pack (i.e., {}) must be eight.", unpacked.size()); -#if HAVE_SSE - // Broadcast 8 bit value in all 8-bit registers. - __m64 mask = _mm_set1_pi8(static_cast(value)); - // Mask bits of interest for each 8-bit register. - mask = _mm_and_si64(mask, _mm_set_pi8(1, 2, 4, 8, 16, 32, 64, -128)); + // Unpack a byte by copying each bit into a byte MSB within a 64-bit register. + uint64_t unpacked_ = ((static_cast(value) * 0x8040201008040201) & 0x8080808080808080) >> 7UL; - // Convert to a mask. - mask = ~_mm_cmpeq_pi8(mask, _mm_setzero_si64()); - - // Select least significant bits. - mask = _mm_and_si64(mask, _mm_set1_pi8(1)); - - // Get mask and write - *reinterpret_cast<__m64*>(unpacked.data()) = mask; -#else - for (unsigned i_bit = 0, i_bit_end = unpacked.size(); i_bit != i_bit_end; ++i_bit) { - unpacked[i_bit] = static_cast(value >> ((i_bit_end - 1) - i_bit)) & 1U; - } -#endif + // Store the unpacked data. + std::memcpy(unpacked.data(), &unpacked_, sizeof(uint64_t)); } template RetType pack_8bit(span unpacked) { srsran_assert(unpacked.size() == 8, "The amount of data to pack (i.e., {}) must be eight.", unpacked.size()); -#if HAVE_SSE - __m64 mask = _mm_cmpgt_pi8(*(reinterpret_cast(unpacked.data())), _mm_set1_pi8(0)); - // Reverse - mask = _mm_shuffle_pi8(mask, _mm_set_pi8(0, 1, 2, 3, 4, 5, 6, 7)); + // Load unpacked data. + uint64_t unpacked_ = 0; + std::memcpy(&unpacked_, unpacked.data(), sizeof(uint64_t)); - // Get mask and write - return static_cast(_mm_movemask_pi8(mask)); -#else - RetType packed = 0; + // Mask the unpacked bits. + unpacked_ &= 0x101010101010101UL; - for (unsigned i = 0; i < unpacked.size(); i++) { - packed |= static_cast(unpacked[i] << (8 - i - 1U)); - } + // Pack data and select the first eight MSB. + uint64_t packed = (unpacked_ * 0x8040201008040201UL) >> 56UL; - return packed; -#endif + return static_cast(packed); } } // namespace @@ -97,93 +78,93 @@ span srsran::srsvec::bit_unpack(span bits, unsigned value, uns return bits.last(bits.size() - nof_bits); } -void srsran::srsvec::bit_unpack(span unpacked, span packed) -{ - unsigned nbits = unpacked.size(); - unsigned nbytes = packed.size(); - unsigned i; - - srsran_assert(divide_ceil(nbits, 8) == nbytes, "Inconsistent input sizes"); - - for (i = 0; i < nbytes; i++) { - unpacked = bit_unpack(unpacked, packed[i], 8); - } - if (nbits % 8) { - bit_unpack(unpacked, packed[i] >> (8 - nbits % 8), nbits % 8); - } -} - void srsran::srsvec::bit_unpack(span unpacked, const bit_buffer& packed) { srsran_assert(packed.size() == unpacked.size(), "The packed number of bits (i.e.{}) must be equal to the number of unpacked bits (i.e., {}).", packed.size(), unpacked.size()); - // Unpack each byte. - unsigned bit_offset = 0; - unsigned i_byte = 0; + // Read/write byte index. + unsigned i_byte = 0; + +#if defined(__AVX512F__) && defined(__AVX512VBMI__) && defined(__AVX512BW__) + const __mmask64* packed_ptr = reinterpret_cast(packed.get_buffer().data()); + for (unsigned i_byte_end = (packed.size() / 64) * 8; i_byte != i_byte_end; i_byte += 8) { + // Load 64 bits in a go as a mask. + __mmask64 blend_mask = *(packed_ptr++); + + // Load 64 bits in a go. + __m512i reg = _mm512_mask_blend_epi8(blend_mask, _mm512_set1_epi8(0), _mm512_set1_epi8(1)); + + // Reverses bits within bytes. + __m512i permute_mask = _mm512_set_epi8( + // clang-format off + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + // clang-format on + ); + reg = _mm512_permutexvar_epi8(permute_mask, reg); + + // Store register. + _mm512_storeu_si512(reinterpret_cast<__m512i*>(unpacked.data()), reg); + + // Advance unpacked buffer. + unpacked = unpacked.last(unpacked.size() - 64); + } +#endif // defined(__AVX512F__) && defined(__AVX512VBMI__) && defined(__AVX512BW__) -#ifdef HAVE_AVX2 +#if defined(__AVX__) && defined(__AVX2__) span packed_buffer = packed.get_buffer(); - for (unsigned i_byte_end = (packed.size() / 32) * 4; i_byte != i_byte_end; i_byte += 4, bit_offset += 32) { + for (unsigned i_byte_end = (packed.size() / 32) * 4; i_byte != i_byte_end; i_byte += 4) { + // Load input in different registers. __m256i in = _mm256_setzero_si256(); - - in = _mm256_insert_epi8(in, packed_buffer[i_byte + 0], 0); - in = _mm256_insert_epi8(in, packed_buffer[i_byte + 1], 8); - in = _mm256_insert_epi8(in, packed_buffer[i_byte + 2], 16); - in = _mm256_insert_epi8(in, packed_buffer[i_byte + 3], 24); - - in = _mm256_shuffle_epi8( - in, - _mm256_setr_epi8( - 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8)); - - __m256i mask = _mm256_set_epi8(1, - 2, - 4, - 8, - 16, - 32, - 64, - -128, - 1, - 2, - 4, - 8, - 16, - 32, - 64, - -128, - 1, - 2, - 4, - 8, - 16, - 32, - 64, - -128, - 1, - 2, - 4, - 8, - 16, - 32, - 64, - -128); - + in = _mm256_insert_epi8(in, packed_buffer[i_byte + 0], 0); + in = _mm256_insert_epi8(in, packed_buffer[i_byte + 1], 8); + in = _mm256_insert_epi8(in, packed_buffer[i_byte + 2], 16); + in = _mm256_insert_epi8(in, packed_buffer[i_byte + 3], 24); + + // Repeats each byte 8 times. + __m256i shuffle_mask = _mm256_setr_epi8( + // clang-format off + 0, 0, 0, 0, 0, 0, 0, 0, + 8, 8, 8, 8, 8, 8, 8, 8, + 0, 0, 0, 0, 0, 0, 0, 0, + 8, 8, 8, 8, 8, 8, 8, 8 + // clang-format off + ); + in = _mm256_shuffle_epi8(in, shuffle_mask); + + // Selects each of the bits. + __m256i mask = _mm256_set_epi8( + // clang-format off + 1, 2, 4, 8, 16, 32, 64, -128, + 1, 2, 4, 8, 16, 32, 64, -128, + 1, 2, 4, 8, 16, 32, 64, -128, + 1, 2, 4, 8, 16, 32, 64, -128 + // clang-format on + ); mask = _mm256_and_si256(mask, in); + + // Moves the selected bit to the LSB of each output byte. mask = ~_mm256_cmpeq_epi8(mask, _mm256_setzero_si256()); mask = _mm256_and_si256(mask, _mm256_set1_epi8(1)); + // Store register. _mm256_storeu_si256(reinterpret_cast<__m256i*>(unpacked.data()), mask); // Advance unpacked buffer. unpacked = unpacked.last(unpacked.size() - 32); } -#endif // HAVE_AVX2 +#endif // defined(__AVX__) && defined(__AVX2__) - for (unsigned i_byte_end = packed.size() / 8; i_byte != i_byte_end; ++i_byte, bit_offset += 8) { + for (unsigned i_byte_end = packed.size() / 8; i_byte != i_byte_end; ++i_byte) { // Extract byte. uint8_t byte = packed.get_byte(i_byte); // Unpack byte. @@ -196,11 +177,28 @@ void srsran::srsvec::bit_unpack(span unpacked, const bit_buffer& packed if (!unpacked.empty()) { std::array temp_unpacked; span unpacked2 = temp_unpacked; - unpack_8bit(unpacked2, packed.extract(bit_offset, unpacked.size())); + unpack_8bit(unpacked2, packed.extract(8 * i_byte, unpacked.size())); srsvec::copy(unpacked, unpacked2.last(unpacked.size())); } } +void srsran::srsvec::bit_unpack(span unpacked, const bit_buffer& packed, unsigned offset) +{ + // Calculate the number of bits to align the packed data to byte boundary. + unsigned nof_head_bits = std::min((8 - (offset % 8)) % 8, static_cast(unpacked.size())); + if (nof_head_bits != 0) { + // Extract the alignment bits. + uint8_t head_bits = packed.extract(offset, nof_head_bits); + + // Unpack the head. + unpacked = bit_unpack(unpacked, head_bits, nof_head_bits); + } + + unsigned aligned_offset = divide_ceil(offset, 8) * 8; + const bit_buffer aligned_packed = packed.last(packed.size() - aligned_offset).first(unpacked.size()); + bit_unpack(unpacked, aligned_packed); +} + unsigned srsran::srsvec::bit_pack(span& bits, unsigned nof_bits) { srsran_assert(nof_bits <= 32U, "Number of bits ({}) exceeds maximum (32).", nof_bits); @@ -230,23 +228,43 @@ unsigned srsran::srsvec::bit_pack(span bits) return value; } -void srsran::srsvec::bit_pack(span packed, span unpacked) -{ - srsran_assert(divide_ceil(unpacked.size(), 8) == packed.size(), "Inconsistent input sizes."); - - for (uint8_t& byte : packed) { - byte = pack_8bit(unpacked.first(8)); - unpacked = unpacked.last(unpacked.size() - 8); - } -} - void srsran::srsvec::bit_pack(bit_buffer& packed, span unpacked) { srsran_assert(packed.size() == unpacked.size(), "The packed number of bits (i.e.{}) must be equal to the number of unpacked bits (i.e., {}).", packed.size(), unpacked.size()); - for (unsigned i_byte = 0, i_byte_end = unpacked.size() / 8; i_byte != i_byte_end; ++i_byte) { + unsigned i_byte = 0; + +#if defined(__AVX512F__) && defined(__AVX512VBMI__) && defined(__AVX512BW__) + __mmask64* packed_ptr = reinterpret_cast<__mmask64*>(packed.get_buffer().data()); + for (unsigned i_byte_end = (packed.size() / 64) * 8; i_byte != i_byte_end; i_byte += 8) { + // Load the 64 input values into an AVX-512 register. + __m512i reg = _mm512_loadu_si512(reinterpret_cast(unpacked.data())); + + // Reverses bits within bytes. + __m512i permute_mask = _mm512_set_epi8( + // clang-format off + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + // clang-format on + ); + reg = _mm512_permutexvar_epi8(permute_mask, reg); + + // Extract the LSBs using a bitwise AND operation. + *(packed_ptr++) = _mm512_test_epi8_mask(reg, _mm512_set1_epi8(1)); + + unpacked = unpacked.last(unpacked.size() - 64); + } +#endif // defined(__AVX512F__) && defined(__AVX512VBMI__) && defined(__AVX512BW__) + + for (unsigned i_byte_end = packed.size() / 8; i_byte != i_byte_end; ++i_byte) { unsigned byte = pack_8bit(unpacked.first(8)); unpacked = unpacked.last(unpacked.size() - 8); packed.set_byte(byte, i_byte); @@ -264,6 +282,23 @@ void srsran::srsvec::bit_pack(bit_buffer& packed, span unpacked) } } +void srsran::srsvec::bit_pack(srsran::bit_buffer& packed, unsigned offset, span unpacked) +{ + // Calculate the number of bits to align the packed data to byte boundary. + unsigned nof_head_bits = std::min((8 - (offset % 8)) % 8, static_cast(unpacked.size())); + if (nof_head_bits != 0) { + // Pack the bits of the head. + unsigned head_bits = bit_pack(unpacked, nof_head_bits); + + // Insert the packed head bits. + packed.insert(head_bits, offset, nof_head_bits); + } + + unsigned aligned_offset = divide_ceil(offset, 8) * 8; + bit_buffer aligned_packed = packed.last(packed.size() - aligned_offset).first(unpacked.size()); + bit_pack(aligned_packed, unpacked); +} + void srsran::srsvec::copy_offset(srsran::bit_buffer& output, span input, unsigned startpos) { static constexpr unsigned bits_per_word = 8; @@ -281,7 +316,7 @@ void srsran::srsvec::copy_offset(srsran::bit_buffer& output, span std::copy_n(input.begin() + input_start_word, nof_full_words, buffer.begin()); } else { unsigned i_word = 0; -#ifdef HAVE_AVX2 +#ifdef __AVX2__ for (unsigned i_word_end = (nof_full_words / 32) * 32; i_word != i_word_end; i_word += 32) { __m256i word0 = _mm256_loadu_si256(reinterpret_cast(&input[input_start_word + i_word])); __m256i word1 = _mm256_loadu_si256(reinterpret_cast(&input[input_start_word + i_word + 1])); @@ -292,7 +327,7 @@ void srsran::srsvec::copy_offset(srsran::bit_buffer& output, span __m256i word = _mm256_or_si256(word0, word1); _mm256_storeu_si256(reinterpret_cast<__m256i*>(&buffer[i_word]), word); } -#endif // HAVE_AVX2 +#endif // __AVX2__ for (; i_word != nof_full_words; ++i_word) { extended_word_t input_word = static_cast(input[input_start_word + i_word]) << bits_per_word; input_word |= static_cast(input[input_start_word + i_word + 1]); diff --git a/lib/support/bit_encoding.cpp b/lib/support/bit_encoding.cpp index b500492409..216ee2c129 100644 --- a/lib/support/bit_encoding.cpp +++ b/lib/support/bit_encoding.cpp @@ -25,12 +25,14 @@ using namespace srsran; -void bit_encoder::pack(uint64_t val, uint32_t n_bits) +bool bit_encoder::pack(uint64_t val, uint32_t n_bits) { srsran_assert(n_bits <= 64U, "Invalid number of bits={} passed to pack()", n_bits); while (n_bits > 0U) { if (offset == 0U) { - writer.append(0U); + if (not writer.append(0U)) { + return false; + } } // apply mask if required if (n_bits < 64U) { @@ -51,6 +53,7 @@ void bit_encoder::pack(uint64_t val, uint32_t n_bits) offset = 0U; } } + return true; } void bit_encoder::pack_bytes(srsran::span bytes) diff --git a/lib/support/event_tracing.cpp b/lib/support/event_tracing.cpp index d23045b506..f55748f7ee 100644 --- a/lib/support/event_tracing.cpp +++ b/lib/support/event_tracing.cpp @@ -22,6 +22,7 @@ #include "srsran/support/event_tracing.h" #include "srsran/srslog/srslog.h" +#include "srsran/support/executors/task_worker.h" #include "srsran/support/format_utils.h" #include "srsran/support/unique_thread.h" #include @@ -37,7 +38,8 @@ trace_point run_epoch = trace_clock::now(); class event_trace_writer { public: - explicit event_trace_writer(const char* trace_file) : fptr(fopen(trace_file, "w")) + explicit event_trace_writer(const char* trace_file) : + fptr(fopen(trace_file, "w")), trace_worker("tracer_worker", 2048, std::chrono::microseconds{200}) { if (fptr == nullptr) { report_fatal_error("ERROR: Failed to open trace file {}", trace_file); @@ -51,25 +53,39 @@ class event_trace_writer ~event_trace_writer() { + trace_worker.wait_pending_tasks(); + trace_worker.stop(); fmt::print(fptr, "\n]"); fclose(fptr); } template - void write_trace(EventType&& ev) + void write_trace(const EventType& ev) { - if (SRSRAN_LIKELY(not first_entry)) { - fmt::print(fptr, ",\n{}", std::forward(ev)); - } else { - fmt::print(fptr, "\n{}", std::forward(ev)); - first_entry = false; + if (not trace_worker.push_task([this, ev]() { + if (SRSRAN_LIKELY(not first_entry)) { + fmt::print(fptr, ",\n{}", ev); + } else { + fmt::print(fptr, "\n{}", ev); + first_entry = false; + } + })) { + if (not warn_logged.exchange(true, std::memory_order_relaxed)) { + file_event_tracer warn_tracer; + warn_tracer << instant_trace_event{"trace_overflow", instant_trace_event::cpu_scope::global}; + srslog::fetch_basic_logger("ALL").warning("Tracing thread cannot keep up with the number of events."); + } } } private: FILE* fptr; - bool first_entry = true; + // Task worker to process events. + general_task_worker trace_worker; + + bool first_entry = true; + std::atomic warn_logged{false}; }; /// Unique event trace file writer. diff --git a/lib/support/network/io_broker_epoll.cpp b/lib/support/network/io_broker_epoll.cpp index 2dc3d358f0..06c66698f7 100644 --- a/lib/support/network/io_broker_epoll.cpp +++ b/lib/support/network/io_broker_epoll.cpp @@ -41,7 +41,7 @@ io_broker_epoll::io_broker_epoll(io_broker_config config) : logger(srslog::fetch }); // start thread to handle epoll events - thread = unique_thread(config.thread_name, config.thread_prio, [this]() { + thread = unique_thread(config.thread_name, config.thread_prio, config.cpu_mask, [this]() { while (running) { thread_loop(); } diff --git a/lib/support/network/io_broker_epoll.h b/lib/support/network/io_broker_epoll.h index ec53458a6b..8ded8a3071 100644 --- a/lib/support/network/io_broker_epoll.h +++ b/lib/support/network/io_broker_epoll.h @@ -30,11 +30,6 @@ namespace srsran { -struct io_broker_config { - std::string thread_name = "io_broker_epoll"; - os_thread_realtime_priority thread_prio = os_thread_realtime_priority::no_realtime(); -}; - /// @brief Implementation of an IO broker using epoll. class io_broker_epoll : public io_broker { diff --git a/lib/support/network/io_broker_factory.cpp b/lib/support/network/io_broker_factory.cpp index 5b432d383d..2d2a75e429 100644 --- a/lib/support/network/io_broker_factory.cpp +++ b/lib/support/network/io_broker_factory.cpp @@ -25,11 +25,11 @@ using namespace srsran; -std::unique_ptr srsran::create_io_broker(io_broker_type type) +std::unique_ptr srsran::create_io_broker(io_broker_type type, io_broker_config config) { switch (type) { case io_broker_type::epoll: - return std::make_unique(io_broker_config()); + return std::make_unique(config); default: srsran_terminate("IO broker type not supported"); } diff --git a/lib/support/task_execution_manager.cpp b/lib/support/task_execution_manager.cpp index 52f9e4950f..ca86595ea1 100644 --- a/lib/support/task_execution_manager.cpp +++ b/lib/support/task_execution_manager.cpp @@ -22,7 +22,7 @@ #include "srsran/support/executors/task_execution_manager.h" #include "srsran/support/executors/executor_tracer.h" -#include "srsran/support/executors/priority_multiqueue_task_worker.h" +#include "srsran/support/executors/priority_task_worker.h" #include "srsran/support/executors/sync_task_executor.h" #include "srsran/support/executors/task_worker.h" #include "srsran/support/executors/task_worker_pool.h" @@ -187,6 +187,14 @@ srsran::create_execution_context(const execution_config_helper::single_worker& p return single_worker_context::create( params.name, params); } break; + case concurrent_queue_policy::lockfree_mpmc: { + if (not params.wait_sleep_time.has_value()) { + srslog::fetch_basic_logger("ALL").error("Wait sleep time is required for lockfree_mpmc queue policy"); + return nullptr; + } + return single_worker_context::create( + params.name, params); + } break; default: srslog::fetch_basic_logger("ALL").error("Unknown queue policy"); break; @@ -199,14 +207,40 @@ srsran::create_execution_context(const execution_config_helper::single_worker& p namespace { /// Execution context for a task worker pool. +template struct worker_pool_context final - : public common_task_execution_context { - using base_type = common_task_execution_context; + : public common_task_execution_context, + execution_config_helper::worker_pool> { + static_assert(QueuePolicy == concurrent_queue_policy::lockfree_mpmc or + QueuePolicy == concurrent_queue_policy::locking_mpmc, + "Invalid queue policy"); + + using worker_type = task_worker_pool; + using executor_type = task_worker_pool_executor; + using base_type = common_task_execution_context; + + template = 0> + worker_pool_context(const execution_config_helper::worker_pool& params) : + base_type(params.tracer, + params.nof_workers, + params.queue.size, + params.name, + params.sleep_time.value(), + params.prio, + params.masks) + { + } + template = 0> + worker_pool_context(const execution_config_helper::worker_pool& params) : + base_type(params.tracer, params.nof_workers, params.queue.size, params.name, params.prio, params.masks) + { + } static std::unique_ptr create(const execution_config_helper::worker_pool& params) { - auto ctxt = std::make_unique( - params.tracer, params.nof_workers, params.queue.size, params.name, params.prio, params.masks); + auto ctxt = std::make_unique(params); if (ctxt == nullptr or not ctxt->add_executors(params.executors)) { return nullptr; } @@ -220,7 +254,7 @@ struct worker_pool_context final std::unique_ptr create_executor(const execution_config_helper::worker_pool::executor& desc) override { - task_worker_pool_executor exec(this->worker); + executor_type exec(this->worker); return this->task_tracer == nullptr ? decorate_executor(desc, std::move(exec)) : decorate_executor(desc, make_trace_executor(desc.name, std::move(exec), *this->task_tracer)); @@ -232,12 +266,22 @@ struct worker_pool_context final std::unique_ptr srsran::create_execution_context(const execution_config_helper::worker_pool& params) { - if (params.queue.policy != srsran::concurrent_queue_policy::locking_mpmc) { - srslog::fetch_basic_logger("ALL").error( - "Only locking_mpmc queue policy is supported for worker pools at the moment"); - return nullptr; + switch (params.queue.policy) { + case concurrent_queue_policy::locking_mpmc: + if (params.sleep_time.has_value()) { + srslog::fetch_basic_logger("ALL").error("Wait sleep time is not supported for locking_mpmc queue policy"); + } + return worker_pool_context::create(params); + case concurrent_queue_policy::lockfree_mpmc: + if (not params.sleep_time.has_value()) { + srslog::fetch_basic_logger("ALL").error("Wait sleep time is required for lockfree_mpmc queue policy"); + } + return worker_pool_context::create(params); + default: + srslog::fetch_basic_logger("ALL").error("Only MPMC queue policies are supported for worker pools"); + break; } - return worker_pool_context::create(params); + return nullptr; } /* /////////////////////////////////////////////////////////////////////////////////////////////// */ @@ -246,9 +290,9 @@ namespace { template struct priority_multiqueue_worker_context - : public common_task_execution_context, + : public common_task_execution_context, execution_config_helper::priority_multiqueue_worker> { - using base_type = common_task_execution_context, + using base_type = common_task_execution_context, execution_config_helper::priority_multiqueue_worker>; static std::unique_ptr @@ -274,16 +318,8 @@ struct priority_multiqueue_worker_context std::unique_ptr create_executor(const execution_config_helper::priority_multiqueue_worker::executor& desc) override { - if ((int)desc.priority > 0) { - this->logger.error("Invalid priority multiqueue task worker executor priority {}."); - return nullptr; - } - // Convert one task priority type to the other. - size_t queue_idx = std::min(static_cast(-static_cast(desc.priority)), sizeof...(QueuePolicies) - 1); - const srsran::task_queue_priority queue_prio = static_cast(queue_idx); - std::unique_ptr exec; - visit_executor(this->worker, queue_prio, [this, &exec, &desc](auto&& prio_exec) { + visit_executor(this->worker, desc.priority, [this, &exec, &desc](auto&& prio_exec) { exec = this->task_tracer == nullptr ? decorate_executor(desc, std::move(prio_exec)) : decorate_executor(desc, make_trace_executor(desc.name, std::move(prio_exec), *this->task_tracer)); @@ -292,20 +328,24 @@ struct priority_multiqueue_worker_context } }; +static constexpr size_t MAX_QUEUES_PER_PRIORITY_MULTIQUEUE_WORKER = 4U; + // Special case to stop recursion for task queue policies. -template = 4, int> = 0> +template = MAX_QUEUES_PER_PRIORITY_MULTIQUEUE_WORKER, int> = 0> std::unique_ptr create_execution_context_helper(const execution_config_helper::priority_multiqueue_worker& params) { - report_fatal_error("Workers with more than 3 queues are not supported"); + report_fatal_error("Workers with equal or more than {} queues are not supported", + MAX_QUEUES_PER_PRIORITY_MULTIQUEUE_WORKER); return nullptr; }; -template = 0> +template = 0> std::unique_ptr create_execution_context_helper(const execution_config_helper::priority_multiqueue_worker& params) { - static_assert(sizeof...(QueuePolicies) < 32, "Too many queue policies"); size_t vec_size = sizeof...(QueuePolicies); if (vec_size > params.queues.size()) { return nullptr; @@ -321,6 +361,8 @@ create_execution_context_helper(const execution_config_helper::priority_multique return create_execution_context_helper(params); case concurrent_queue_policy::locking_mpsc: return create_execution_context_helper(params); + case concurrent_queue_policy::lockfree_mpmc: + return create_execution_context_helper(params); default: srslog::fetch_basic_logger("ALL").error("Unknown queue policy"); break; @@ -334,7 +376,6 @@ std::unique_ptr srsran::create_execution_context(const execution_config_helper::priority_multiqueue_worker& params) { return create_execution_context_helper<>(params); - // return priority_multiqueue_worker_context::create(params); } /* /////////////////////////////////////////////////////////////////////////////////////////////// */ diff --git a/lib/support/task_worker.cpp b/lib/support/task_worker.cpp index 318c5eeddf..db38a23a51 100644 --- a/lib/support/task_worker.cpp +++ b/lib/support/task_worker.cpp @@ -47,7 +47,7 @@ unique_function general_task_worker::make_block auto& logger = srslog::fetch_basic_logger("ALL"); logger.info("Task worker \"{}\" started...", this_thread_name()); while (true) { - if (not pending_tasks.pop_blocking([](const unique_task& task) { task(); })) { + if (not pending_tasks.call_on_pop_blocking([](const unique_task& task) { task(); })) { break; } } @@ -71,3 +71,4 @@ template class srsran::general_task_worker; template class srsran::general_task_worker; +template class srsran::general_task_worker; diff --git a/lib/support/task_worker_pool.cpp b/lib/support/task_worker_pool.cpp index 506d73ad9d..bbe3305f8c 100644 --- a/lib/support/task_worker_pool.cpp +++ b/lib/support/task_worker_pool.cpp @@ -25,63 +25,68 @@ using namespace srsran; -task_worker_pool::task_worker_pool(unsigned nof_workers_, - unsigned queue_size, - const std::string& worker_name_prefix, - os_thread_realtime_priority prio_, - span cpu_masks) : - pool_name(worker_name_prefix), - logger(srslog::fetch_basic_logger("ALL")), - workers(nof_workers_), - pending_tasks(queue_size) +template +void task_worker_pool::start_impl(os_thread_realtime_priority prio_, + span cpu_masks) { if (cpu_masks.size() > 1) { // An array with a single mask is allowed, otherwise the number of masks must be equal to the number of workers. - srsran_assert(cpu_masks.size() == nof_workers_, "Wrong array of CPU masks provided"); + srsran_assert(cpu_masks.size() == workers.size(), "Wrong array of CPU masks provided"); } - for (unsigned i = 0; i != nof_workers_; ++i) { + for (unsigned i = 0; i != workers.size(); ++i) { auto task_func = [this, i]() { while (true) { - bool success; - unique_task task = pending_tasks.pop_blocking(&success); - if (not success) { + optional task = pending_tasks.pop_blocking(); + if (not task.has_value()) { break; } - task(); + (*task)(); } logger.info("Task worker \"{}\" finished.", workers[i].t_handle.get_name()); }; if (cpu_masks.empty()) { - workers[i].t_handle = unique_thread{fmt::format("{}#{}", worker_name_prefix, i), prio_, task_func}; + workers[i].t_handle = unique_thread{fmt::format("{}#{}", pool_name, i), prio_, task_func}; } else { // Check whether a single mask for all workers should be used. os_sched_affinity_bitmask cpu_mask = (cpu_masks.size() == 1) ? cpu_masks[0] : cpu_masks[i]; - workers[i].t_handle = unique_thread{fmt::format("{}#{}", worker_name_prefix, i), prio_, cpu_mask, task_func}; + workers[i].t_handle = unique_thread{fmt::format("{}#{}", pool_name, i), prio_, cpu_mask, task_func}; } } } -task_worker_pool::~task_worker_pool() +template +task_worker_pool::~task_worker_pool() { stop(); } -void task_worker_pool::stop() +template +void task_worker_pool::stop() { - if (not pending_tasks.is_stopped()) { - pending_tasks.stop(); - for (auto& w : workers) { + for (worker& w : workers) { + if (w.t_handle.running()) { + pending_tasks.request_stop(); w.t_handle.join(); } } } /// \brief Wait for all the currently enqueued tasks to complete. -void task_worker_pool::wait_pending_tasks() +template +void task_worker_pool::wait_pending_tasks() { - std::packaged_task pkg_task([]() { /* do nothing */ }); - std::future fut = pkg_task.get_future(); - push_task(std::move(pkg_task)); - // blocks for enqueued task to complete. - fut.get(); + while (workers[0].t_handle.running()) { + std::packaged_task pkg_task([]() { /* do nothing */ }); + std::future fut = pkg_task.get_future(); + if (push_task(std::move(pkg_task))) { + // blocks for enqueued task to complete. + fut.get(); + return; + } + // Keep trying to push the task until it succeeds. + std::this_thread::sleep_for(std::chrono::microseconds{50}); + } } + +template class srsran::task_worker_pool; +template class srsran::task_worker_pool; diff --git a/tests/benchmarks/CMakeLists.txt b/tests/benchmarks/CMakeLists.txt index 3591950118..fe0aef9e96 100644 --- a/tests/benchmarks/CMakeLists.txt +++ b/tests/benchmarks/CMakeLists.txt @@ -22,6 +22,7 @@ remove_definitions(-DASSERTS_ENABLED) remove_definitions(-DPARANOID_ASSERTS_ENABLED) add_subdirectory(adt) +add_subdirectory(gateways) add_subdirectory(phy) add_subdirectory(scheduler) add_subdirectory(du_high) diff --git a/tests/benchmarks/gateways/CMakeLists.txt b/tests/benchmarks/gateways/CMakeLists.txt new file mode 100644 index 0000000000..83d4000041 --- /dev/null +++ b/tests/benchmarks/gateways/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Software Radio Systems Limited +# +# This file is part of srsRAN +# +# srsRAN is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# srsRAN is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# A copy of the GNU Affero General Public License can be found in +# the LICENSE file in the top-level directory of this distribution +# and at http://www.gnu.org/licenses/. +# + +set_directory_properties(PROPERTIES LABELS "gateways") + +include_directories(../../..) + +add_executable(udp_network_gateway_benchmark udp_network_gateway_benchmark.cpp) +target_link_libraries(udp_network_gateway_benchmark srsran_network srsran_gateway srsran_support srslog) diff --git a/tests/benchmarks/gateways/udp_network_gateway_benchmark.cpp b/tests/benchmarks/gateways/udp_network_gateway_benchmark.cpp new file mode 100644 index 0000000000..0c7a369e07 --- /dev/null +++ b/tests/benchmarks/gateways/udp_network_gateway_benchmark.cpp @@ -0,0 +1,219 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srsran/gateways/udp_network_gateway_factory.h" +#include "srsran/srslog/srslog.h" +#include "srsran/support/io/io_broker_factory.h" +#include +#include +#include + +using namespace srsran; + +struct bench_params { + unsigned pdu_len = 1400; + unsigned nof_pdus = 100000; + unsigned slow_inter_rx_us = 500; +}; + +static void usage(const char* prog, const bench_params& params) +{ + fmt::print("Usage: {} [-n ] [-l ] [-u ]\n", prog); + fmt::print("\t-l PDU len [Default {}]\n", params.pdu_len); + fmt::print("\t-n Number of PDUs [Default {}]\n", params.nof_pdus); + fmt::print("\t-u Notify large PDU inter arrival time longer than t microseconds [Default {}]\n", + params.slow_inter_rx_us); + fmt::print("\t-h Show this message\n"); +} + +static void parse_args(int argc, char** argv, bench_params& params) +{ + int opt = 0; + while ((opt = getopt(argc, argv, "l:n:u:h")) != -1) { + switch (opt) { + case 'l': + params.pdu_len = std::strtol(optarg, nullptr, 10); + break; + case 'n': + params.nof_pdus = std::strtol(optarg, nullptr, 10); + break; + case 'u': + params.slow_inter_rx_us = std::strtol(optarg, nullptr, 10); + case 'h': + default: + usage(argv[0], params); + exit(0); + } + } +} + +class dummy_network_gateway_data_notifier_with_src_addr : public network_gateway_data_notifier_with_src_addr +{ +public: + dummy_network_gateway_data_notifier_with_src_addr(const bench_params& params_) : params(params_) {} + + void on_new_pdu(byte_buffer pdu, const sockaddr_storage& src_addr) override + { + rx_bytes += pdu.length(); + n_pdus++; + + static bool first = true; + auto t_now = std::chrono::high_resolution_clock::now(); + if (!first) { + auto duration = std::chrono::duration_cast(t_now - t_last); + if (duration < t_min) { + t_min = duration; + } + if (duration > t_max) { + t_max = duration; + } + t_sum += duration; + if (duration.count() > params.slow_inter_rx_us) { + fmt::print("Long inter Rx interval t={}us at n_pdus={}\n", duration.count(), n_pdus); + } + } else { + first = false; + } + + t_last = t_now; + } + + unsigned get_rx_bytes() { return rx_bytes; } + unsigned get_n_pdus() { return n_pdus; } + + std::chrono::microseconds get_t_min() { return t_min; } + std::chrono::microseconds get_t_max() { return t_max; } + std::chrono::microseconds get_t_sum() { return t_sum; } + +private: + const bench_params& params; + + unsigned rx_bytes = 0; + unsigned n_pdus = 0; + + std::chrono::high_resolution_clock::time_point t_last = std::chrono::high_resolution_clock::now(); + std::chrono::microseconds t_min = std::chrono::microseconds::max(); + std::chrono::microseconds t_max = std::chrono::microseconds::min(); + std::chrono::microseconds t_sum = std::chrono::microseconds::zero(); + ; +}; + +byte_buffer make_tx_byte_buffer(uint32_t length) +{ + byte_buffer pdu{}; + for (uint32_t i = 0; i < length; ++i) { + pdu.append((uint8_t)i); + } + return pdu; +} + +sockaddr_storage to_sockaddr_storage(std::string dest_addr, uint16_t port) +{ + in_addr inaddr_v4 = {}; + in6_addr inaddr_v6 = {}; + sockaddr_storage addr_storage = {}; + + if (inet_pton(AF_INET, dest_addr.c_str(), &inaddr_v4) == 1) { + sockaddr_in* tmp = (sockaddr_in*)&addr_storage; + tmp->sin_family = AF_INET; + tmp->sin_addr = inaddr_v4; + tmp->sin_port = htons(port); + } else if (inet_pton(AF_INET6, dest_addr.c_str(), &inaddr_v6) == 1) { + sockaddr_in6* tmp = (sockaddr_in6*)&addr_storage; + tmp->sin6_family = AF_INET6; + tmp->sin6_addr = inaddr_v6; + tmp->sin6_port = htons(port); + } + return addr_storage; +} + +int main(int argc, char** argv) +{ + srslog::init(); + + // init GW logger + srslog::fetch_basic_logger("UDP-GW", true).set_level(srslog::basic_levels::warning); + srslog::fetch_basic_logger("UDP-GW", true).set_hex_dump_max_size(100); + + bench_params params{}; + parse_args(argc, argv, params); + + udp_network_gateway_config gw1_cfg; + gw1_cfg.bind_address = "127.0.0.1"; + gw1_cfg.bind_port = 56701; + gw1_cfg.non_blocking_mode = false; + + udp_network_gateway_config gw2_cfg; + gw2_cfg.bind_address = "127.0.0.1"; + gw2_cfg.bind_port = 56702; + gw2_cfg.non_blocking_mode = false; + + dummy_network_gateway_data_notifier_with_src_addr gw1_dn{params}, gw2_dn{params}; + std::unique_ptr gw1, gw2; + + gw1 = create_udp_network_gateway({gw1_cfg, gw1_dn}); + gw2 = create_udp_network_gateway({gw2_cfg, gw2_dn}); + + gw1->create_and_bind(); + gw2->create_and_bind(); + + std::unique_ptr epoll_broker; + + epoll_broker = create_io_broker(io_broker_type::epoll); + + epoll_broker->register_fd(gw1->get_socket_fd(), [&gw1](int fd) { gw1->receive(); }); + epoll_broker->register_fd(gw2->get_socket_fd(), [&gw2](int fd) { gw2->receive(); }); + + sockaddr_storage gw2_addr = to_sockaddr_storage(gw2_cfg.bind_address, gw2_cfg.bind_port); + + byte_buffer pdu = make_tx_byte_buffer(params.pdu_len); + + auto t_start = std::chrono::high_resolution_clock::now(); + + unsigned nof_pdus = params.nof_pdus; + for (unsigned n = 0; n < nof_pdus; n++) { + gw1->handle_pdu(pdu, gw2_addr); + } + auto t_end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(t_end - t_start); + fmt::print("Tx done\n\n"); + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + fmt::print("Tx time: {} us\n", duration.count()); + fmt::print("Tx data rate: {:.2f} Mbit/s\n", (double)pdu.length() * nof_pdus * 8 * 1e-6 / (duration.count() * 1e-6)); + fmt::print("Rx data rate: {:.2f} Mbit/s\n\n", + (double)pdu.length() * gw2_dn.get_n_pdus() * 8 * 1e-6 / (duration.count() * 1e-6)); + + fmt::print("Tx PDU rate: {:.2f} PDU/s\n", (double)nof_pdus / (duration.count() * 1e-6)); + fmt::print("Rx PDU rate: {:.2f} PDU/s\n\n", (double)gw2_dn.get_n_pdus() / (duration.count() * 1e-6)); + + fmt::print("Tx PDUs total: {:>7}\n", nof_pdus); + fmt::print("Rx PDUs total: {:>7} ({:.2f}% lost)\n\n", + gw2_dn.get_n_pdus(), + (1 - ((double)gw2_dn.get_n_pdus() / nof_pdus)) * 100); + + fmt::print("PDU inter arrival time (min/avg/max) [us]: {}/{}/{}\n", + gw2_dn.get_t_min().count(), + gw2_dn.get_t_sum().count() / gw2_dn.get_n_pdus(), + gw2_dn.get_t_max().count()); +} diff --git a/tests/benchmarks/phy/upper/channel_coding/ldpc/ldpc_decoder_benchmark.cpp b/tests/benchmarks/phy/upper/channel_coding/ldpc/ldpc_decoder_benchmark.cpp index 5c0c943235..3d41e0cce0 100644 --- a/tests/benchmarks/phy/upper/channel_coding/ldpc/ldpc_decoder_benchmark.cpp +++ b/tests/benchmarks/phy/upper/channel_coding/ldpc/ldpc_decoder_benchmark.cpp @@ -145,24 +145,26 @@ int main(int argc, char** argv) codeblock.begin(), codeblock.end(), [&]() { return static_cast((rgen() & 1) * 20 - 10); }); } else { // Generate random message, attach its CRC and encode. - std::vector to_encode(msg_length); - std::vector encoded(cb_length); + dynamic_bit_buffer to_encode(msg_length); + dynamic_bit_buffer encoded(cb_length); // Generate a random message. - unsigned msg_len_minus_crc = msg_length - 16; - span msg_span{to_encode.data(), msg_len_minus_crc}; - std::generate(msg_span.begin(), msg_span.end(), [&]() { return static_cast((rgen() & 1)); }); + unsigned msg_len_minus_crc = msg_length - 16; + bit_buffer msg_span = to_encode.first(msg_len_minus_crc); + for (unsigned i_bit = 0; i_bit != msg_length; ++i_bit) { + msg_span.insert(rgen() & 1, i_bit, 1); + } // Add CRC bits at the end. - unsigned checksum = crc16->calculate_bit(msg_span); - srsvec::bit_unpack(span(to_encode.data(), msg_length).last(16), checksum, 16); + unsigned checksum = crc16->calculate(msg_span); + to_encode.insert(checksum, msg_len_minus_crc, 16); // Encode entire message. srsran::codeblock_metadata::tb_common_metadata cfg_enc; cfg_enc = {bg, ls}; encoder->encode(encoded, to_encode, cfg_enc); // Convert codeblock bits to LLRs. - std::transform(encoded.begin(), encoded.end(), codeblock.begin(), [](uint8_t b) { - return log_likelihood_ratio::copysign(10, 1 - 2 * b); - }); + for (unsigned i_bit = 0; i_bit != cb_length; ++i_bit) { + codeblock[i_bit] = log_likelihood_ratio::copysign(10, 1 - 2 * encoded.extract(i_bit, 1)); + } } // Prepare message storage. diff --git a/tests/benchmarks/phy/upper/channel_coding/ldpc/ldpc_encoder_benchmark.cpp b/tests/benchmarks/phy/upper/channel_coding/ldpc/ldpc_encoder_benchmark.cpp index ae4efc77c9..88b01f3368 100644 --- a/tests/benchmarks/phy/upper/channel_coding/ldpc/ldpc_encoder_benchmark.cpp +++ b/tests/benchmarks/phy/upper/channel_coding/ldpc/ldpc_encoder_benchmark.cpp @@ -97,11 +97,13 @@ int main(int argc, char** argv) for (unsigned cb_length : {min_cb_length, max_cb_length}) { // Generate message data. - std::vector data(msg_length); - std::generate(data.begin(), data.end(), [&]() { return static_cast(rgen() & 1); }); + dynamic_bit_buffer data(msg_length); + for (unsigned i_bit = 0; i_bit != msg_length; ++i_bit) { + data.insert(rgen() & 1, i_bit, 1); + } // Generate codeblock. - std::vector codeblock(cb_length); + dynamic_bit_buffer codeblock(cb_length); srsran::codeblock_metadata::tb_common_metadata cfg_enc = {bg, ls}; diff --git a/tests/benchmarks/phy/upper/channel_processors/pdsch_processor_benchmark.cpp b/tests/benchmarks/phy/upper/channel_processors/pdsch_processor_benchmark.cpp index 48cc0aad8c..019e1e931d 100644 --- a/tests/benchmarks/phy/upper/channel_processors/pdsch_processor_benchmark.cpp +++ b/tests/benchmarks/phy/upper/channel_processors/pdsch_processor_benchmark.cpp @@ -20,7 +20,7 @@ * */ -#include "../../../lib/phy/upper/rx_softbuffer_pool_impl.h" +#include "../../../../unittests/phy/upper/channel_processors/pdsch_processor_test_doubles.h" #include "../../../lib/scheduler/support/tbs_calculator.h" #include "srsran/phy/support/support_factories.h" #include "srsran/phy/upper/channel_processors/channel_processor_factories.h" @@ -88,9 +88,9 @@ static dmrs_type dmrs = dmrs_typ static unsigned nof_cdm_groups_without_data = 1; static bounded_bitset dmrs_symbol_mask = {false, false, true, false, false, false, false, true, false, false, false, true, false, false}; -static unsigned nof_pdsch_processor_concurrent_threads = 4; -static std::unique_ptr worker_pool = nullptr; -static std::unique_ptr executor = nullptr; +static unsigned nof_pdsch_processor_concurrent_threads = 4; +static std::unique_ptr> worker_pool = nullptr; +static std::unique_ptr> executor = nullptr; // Thread shared variables. static std::mutex mutex_pending_count; @@ -481,11 +481,17 @@ static pdsch_processor_factory& get_processor_factory() nof_pdsch_processor_concurrent_threads = std::strtol(str.c_str(), nullptr, 10); } - worker_pool = std::make_unique( - nof_pdsch_processor_concurrent_threads, 1024, "pdsch_proc", os_thread_realtime_priority::max()); - executor = std::make_unique(*worker_pool); + // Prepare workers affinities. + std::vector affinity(nof_pdsch_processor_concurrent_threads); + for (unsigned i = 0; i != nof_pdsch_processor_concurrent_threads; ++i) { + affinity[i].set(i + nof_threads); + } + + worker_pool = std::make_unique>( + nof_pdsch_processor_concurrent_threads, 1024, "pdsch_proc", os_thread_realtime_priority::max(), affinity); + executor = std::make_unique>(*worker_pool); - pdsch_proc_factory = create_pdsch_concurrent_processor_factory_sw(ldpc_segm_tx_factory, + pdsch_proc_factory = create_pdsch_concurrent_processor_factory_sw(crc_calc_factory, ldpc_enc_factory, ldpc_rm_factory, prg_factory, @@ -496,6 +502,10 @@ static pdsch_processor_factory& get_processor_factory() } TESTASSERT(pdsch_proc_factory); + // Create PDSCH processor pool. + pdsch_proc_factory = create_pdsch_processor_pool(std::move(pdsch_proc_factory), nof_threads); + TESTASSERT(pdsch_proc_factory); + return *pdsch_proc_factory; } @@ -522,12 +532,11 @@ static std::unique_ptr create_resource_grid(unsigned nof_ports, u return rg_factory->create(nof_ports, nof_symbols, nof_subc); } -static void thread_process(const pdsch_processor::pdu_t& config, span data) +static void thread_process(pdsch_processor& proc, const pdsch_processor::pdu_t& config, span data) { - std::unique_ptr proc = create_processor(); - // Create grid. - std::unique_ptr grid = create_resource_grid(MAX_PORTS, MAX_NSYMB_PER_SLOT, MAX_RB * NRE); + std::unique_ptr grid = + create_resource_grid(config.precoding.get_nof_ports(), MAX_NSYMB_PER_SLOT, MAX_RB * NRE); TESTASSERT(grid); // Notify finish count. @@ -537,12 +546,14 @@ static void thread_process(const pdsch_processor::pdu_t& config, span lock(mutex_pending_count); while (pending_count == 0) { - cvar_count.wait_until(lock, std::chrono::system_clock::now() + std::chrono::milliseconds(2)); + cvar_count.wait_until(lock, std::chrono::system_clock::now() + std::chrono::microseconds(10)); // Quit if signaled. if (thread_quit) { @@ -552,8 +563,20 @@ static void thread_process(const pdsch_processor::pdu_t& config, spanprocess(grid->get_mapper(), {data}, config); + if (worker_pool) { + bool success = worker_pool->push_task( + [&proc, &grid, ¬ifier, &data, &config]() { proc.process(grid->get_mapper(), notifier, {data}, config); }); + (void)success; + } else { + proc.process(grid->get_mapper(), notifier, {data}, config); + } + + // Wait for the processor to finish. + notifier.wait_for_finished(); // Notify finish count. { @@ -590,6 +613,9 @@ int main(int argc, char** argv) // Generate the test cases. std::vector test_case_set = generate_test_cases(selected_profile); + // Create processor. + std::unique_ptr proc = create_processor(); + for (const test_case_type& test_case : test_case_set) { // Get the PDSCH configuration. const pdsch_processor::pdu_t& config = std::get<0>(test_case); @@ -623,8 +649,9 @@ int main(int argc, char** argv) cpuset.set(thread_id); // Create thread. - thread = unique_thread( - "thread_" + std::to_string(thread_id), prio, cpuset, [&config, &data] { thread_process(config, data); }); + thread = unique_thread("thread_" + std::to_string(thread_id), prio, cpuset, [&proc, &config, &data] { + thread_process(*proc, config, data); + }); } // Wait for finish thread init. diff --git a/tests/benchmarks/phy/upper/channel_processors/pusch_processor_benchmark.cpp b/tests/benchmarks/phy/upper/channel_processors/pusch_processor_benchmark.cpp index 92ed3122aa..40ea2fe75f 100644 --- a/tests/benchmarks/phy/upper/channel_processors/pusch_processor_benchmark.cpp +++ b/tests/benchmarks/phy/upper/channel_processors/pusch_processor_benchmark.cpp @@ -26,8 +26,10 @@ #include "srsran/phy/support/resource_grid_writer.h" #include "srsran/phy/support/support_factories.h" #include "srsran/phy/upper/channel_processors/channel_processor_factories.h" +#include "srsran/phy/upper/channel_processors/pusch/pusch_processor_result_notifier.h" #include "srsran/support/benchmark_utils.h" #include "srsran/support/complex_normal_random.h" +#include "srsran/support/executors/task_worker_pool.h" #include "srsran/support/srsran_test.h" #include "srsran/support/unique_thread.h" #include @@ -49,7 +51,19 @@ class pusch_processor_result_notifier_adaptor : public pusch_processor_result_no public: void on_uci(const pusch_processor_result_control& uci) override {} - void on_sch(const pusch_processor_result_data& sch) override {} + void on_sch(const pusch_processor_result_data& sch) override { completed = true; } + + void reset() { completed = false; } + + void wait_for_completion() + { + while (!completed.load()) { + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + } + +private: + std::atomic completed = {false}; }; } // namespace @@ -104,6 +118,9 @@ static dmrs_type dmrs = dmrs_typ static unsigned nof_cdm_groups_without_data = 2; static bounded_bitset dmrs_symbol_mask = {false, false, true, false, false, false, false, false, false, false, false, false, false, false}; +static unsigned nof_pusch_decoder_threads = 8; +static std::unique_ptr> worker_pool = nullptr; +static std::unique_ptr> executor = nullptr; // Thread shared variables. static std::mutex mutex_pending_count; @@ -199,6 +216,14 @@ static const std::vector profile_set = { get_nsymb_per_slot(cyclic_prefix::NORMAL), {270}, {{modulation_scheme::QAM256, 948.0F}}}, + + {"pusch_scs30_100MHz_256qam_max", + "Decodes PUSCH with 50 MHz of bandwidth and a 15 kHz SCS, 256-QAM modulation at maximum code rate.", + subcarrier_spacing::kHz30, + cyclic_prefix::NORMAL, + get_nsymb_per_slot(cyclic_prefix::NORMAL), + {273}, + {{modulation_scheme::QAM256, 948.0F}}}, }; static void usage(const char* prog) @@ -217,6 +242,9 @@ static void usage(const char* prog) fmt::print("\t-R Repetitions [Default {}]\n", nof_repetitions); fmt::print("\t-B Batch size [Default {}]\n", batch_size_per_thread); fmt::print("\t-T Number of threads [Default {}, max. {}]\n", nof_threads, max_nof_threads); + fmt::print("\t-t Number of concurrent PUSCH decoder threads. Set to zero for no concurrency. [Default {}, max. {}]\n", + nof_pusch_decoder_threads, + max_nof_threads); fmt::print("\t-D LDPC decoder type. [Default {}]\n", ldpc_decoder_type); fmt::print("\t-M Rate dematcher type. [Default {}]\n", rate_dematcher_type); fmt::print("\t-E Toggle EVM enable/disable. [Default {}]\n", enable_evm ? "enable" : "disable"); @@ -230,7 +258,7 @@ static void usage(const char* prog) static int parse_args(int argc, char** argv) { int opt = 0; - while ((opt = getopt(argc, argv, "R:T:B:D:M:EP:m:h")) != -1) { + while ((opt = getopt(argc, argv, "R:T:t:B:D:M:EP:m:h")) != -1) { switch (opt) { case 'R': nof_repetitions = std::strtol(optarg, nullptr, 10); @@ -238,6 +266,9 @@ static int parse_args(int argc, char** argv) case 'T': nof_threads = std::min(max_nof_threads, static_cast(std::strtol(optarg, nullptr, 10))); break; + case 't': + nof_pusch_decoder_threads = std::min(max_nof_threads, static_cast(std::strtol(optarg, nullptr, 10))); + break; case 'B': batch_size_per_thread = std::strtol(optarg, nullptr, 10); break; @@ -334,9 +365,14 @@ static std::vector generate_test_cases(const test_profile& profi return test_case_set; } -// Instantiates the PUSCH processor and validator. -static std::tuple, std::unique_ptr> create_processor() +static pusch_processor_factory& get_pusch_processor_factory() { + static std::shared_ptr pusch_proc_factory = nullptr; + + if (pusch_proc_factory) { + return *pusch_proc_factory; + } + // Create pseudo-random sequence generator. std::shared_ptr prg_factory = create_pseudo_random_generator_sw_factory(); TESTASSERT(prg_factory); @@ -394,12 +430,21 @@ static std::tuple, std::unique_ptr demux_factory = create_ulsch_demultiplex_factory_sw(); TESTASSERT(demux_factory); + // Create + if (nof_pusch_decoder_threads != 0) { + worker_pool = std::make_unique>( + nof_pusch_decoder_threads, 1024, "decoder", os_thread_realtime_priority::max()); + executor = std::make_unique>(*worker_pool); + } + // Create PUSCH decoder factory. pusch_decoder_factory_sw_configuration pusch_dec_config; pusch_dec_config.crc_factory = crc_calc_factory; pusch_dec_config.decoder_factory = ldpc_dec_factory; pusch_dec_config.dematcher_factory = ldpc_rm_factory; pusch_dec_config.segmenter_factory = ldpc_segm_rx_factory; + pusch_dec_config.nof_pusch_decoder_threads = nof_threads + nof_pusch_decoder_threads + 1; + pusch_dec_config.executor = executor.get(); std::shared_ptr pusch_dec_factory = create_pusch_decoder_factory_sw(pusch_dec_config); TESTASSERT(pusch_dec_factory); @@ -425,24 +470,37 @@ static std::tuple, std::unique_ptr pusch_proc_factory = - create_pusch_processor_factory_sw(pusch_proc_factory_config); + pusch_proc_factory = create_pusch_processor_factory_sw(pusch_proc_factory_config); + TESTASSERT(pusch_proc_factory); + + pusch_proc_factory = create_pusch_processor_pool(std::move(pusch_proc_factory), nof_threads); TESTASSERT(pusch_proc_factory); + return *pusch_proc_factory; +} + +// Instantiates the PUSCH processor and validator. +static std::tuple, std::unique_ptr> create_processor() +{ + pusch_processor_factory& pusch_proc_factory = get_pusch_processor_factory(); + // Create PUSCH processor. - std::unique_ptr processor = pusch_proc_factory->create(); + std::unique_ptr processor = pusch_proc_factory.create(); TESTASSERT(processor); // Create PUSCH processor validator. - std::unique_ptr validator = pusch_proc_factory->create_validator(); + std::unique_ptr validator = pusch_proc_factory.create_validator(); TESTASSERT(validator); return std::make_tuple(std::move(processor), std::move(validator)); } -static void thread_process(const pusch_processor::pdu_t& config, unsigned tbs, const resource_grid_reader& grid) +static void thread_process(pusch_processor& proc, + const pusch_processor::pdu_t& config, + unsigned tbs, + const resource_grid_reader& grid) { - std::unique_ptr proc(std::get<0>(create_processor())); + pusch_processor_result_notifier_adaptor result_notifier; // Compute the number of codeblocks. unsigned nof_codeblocks = ldpc::compute_nof_codeblocks(units::bits(tbs), config.codeword.value().ldpc_base_graph); @@ -490,9 +548,20 @@ static void thread_process(const pusch_processor::pdu_t& config, unsigned tbs, c // Reserve softbuffer. unique_rx_softbuffer softbuffer = softbuffer_pool->reserve_softbuffer(config.slot, softbuffer_id, nof_codeblocks); + // Reset notifier. + result_notifier.reset(); + // Process PDU. - pusch_processor_result_notifier_adaptor result_notifier; - proc->process(data, softbuffer.get(), result_notifier, grid, config); + if (executor) { + executor->execute([&proc, &data, &softbuffer, &result_notifier, &grid, config]() { + proc.process(data, std::move(softbuffer), result_notifier, grid, config); + }); + } else { + proc.process(data, std::move(softbuffer), result_notifier, grid, config); + } + + // Wait for finish the task. + result_notifier.wait_for_completion(); // Notify finish count. { @@ -568,6 +637,11 @@ int main(int argc, char** argv) } } + // Create processor and validator. + std::unique_ptr processor; + std::unique_ptr validator; + std::tie(processor, validator) = create_processor(); + // Generate the test cases. std::vector test_case_set = generate_test_cases(selected_profile); @@ -577,8 +651,6 @@ int main(int argc, char** argv) // Get the TBS in bits. unsigned tbs = std::get<1>(test_case); - std::unique_ptr validator(std::move(std::get<1>(create_processor()))); - // Make sure the configuration is valid. TESTASSERT(validator->is_valid(config)); @@ -600,9 +672,10 @@ int main(int argc, char** argv) cpuset.set(thread_id); // Create thread. - thread = unique_thread("thread_" + std::to_string(thread_id), prio, cpuset, [&config, &tbs, &grid] { - thread_process(config, tbs, grid.get()->get_reader()); - }); + thread = unique_thread( + "thread_" + std::to_string(thread_id), prio, cpuset, [&proc = *processor, &config, &tbs, &grid] { + thread_process(proc, config, tbs, grid.get()->get_reader()); + }); } // Wait for finish thread init. @@ -670,5 +743,9 @@ int main(int argc, char** argv) perf_meas.print_percentiles_throughput("bits", 1.0 / static_cast(nof_threads)); } + if (worker_pool) { + worker_pool->stop(); + } + return 0; } diff --git a/tests/benchmarks/scheduler/CMakeLists.txt b/tests/benchmarks/scheduler/CMakeLists.txt index 216a32e302..0ddef2eafd 100644 --- a/tests/benchmarks/scheduler/CMakeLists.txt +++ b/tests/benchmarks/scheduler/CMakeLists.txt @@ -24,3 +24,6 @@ include_directories(../../..) add_executable(scheduler_no_ues_benchmark scheduler_no_ues_benchmark.cpp) target_link_libraries(scheduler_no_ues_benchmark srsran_sched srslog sched_config) + +add_executable(scheduler_multi_ue_benchmark scheduler_multi_ue_benchmark.cpp) +target_link_libraries(scheduler_multi_ue_benchmark srsran_sched srslog sched_config) diff --git a/tests/benchmarks/scheduler/scheduler_multi_ue_benchmark.cpp b/tests/benchmarks/scheduler/scheduler_multi_ue_benchmark.cpp new file mode 100644 index 0000000000..fcbc381d73 --- /dev/null +++ b/tests/benchmarks/scheduler/scheduler_multi_ue_benchmark.cpp @@ -0,0 +1,301 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "lib/du_manager/ran_resource_management/du_pucch_resource_manager.h" +#include "scheduler_test_doubles.h" +#include "tests/unittests/scheduler/test_utils/config_generators.h" +#include "srsran/adt/circular_array.h" +#include "srsran/scheduler/scheduler_factory.h" +#include "srsran/support/benchmark_utils.h" +#include "srsran/support/math/gcd.h" +#include + +using namespace srsran; + +struct bench_params { + unsigned nof_repetitions = 100; + unsigned nof_ues = 32; + unsigned dl_bs = 1000000; + unsigned max_dl_grants_per_slot = 100; + bool debug = false; +}; + +static void usage(const char* prog, const bench_params& params) +{ + fmt::print("Usage: {} [-R repetitions] [-u UEs] [-b DL buffer] [-m Max DL UEs per slot] [-d]\n", prog); + fmt::print("\t-R Repetitions [Default {}]\n", params.nof_repetitions); + fmt::print("\t-u Number of UEs [Default {}]\n", params.nof_ues); + fmt::print("\t-b Average RLC DL buffer state [Default {}]\n", params.dl_bs); + fmt::print("\t-m Maximum number of DL UE grants per slot [Default {}]\n", params.max_dl_grants_per_slot); + fmt::print("\t-d Debug mode [Default {}]\n", params.debug); + fmt::print("\t-h Show this message\n"); +} + +static void parse_args(int argc, char** argv, bench_params& params) +{ + int opt = 0; + while ((opt = getopt(argc, argv, "R:u:b:m:d:h")) != -1) { + switch (opt) { + case 'R': + params.nof_repetitions = std::strtol(optarg, nullptr, 10); + break; + case 'u': + params.nof_ues = std::strtol(optarg, nullptr, 10); + break; + case 'b': + params.dl_bs = std::strtol(optarg, nullptr, 10); + break; + case 'm': + params.max_dl_grants_per_slot = std::strtol(optarg, nullptr, 10); + break; + case 'd': + params.debug = std::strtol(optarg, nullptr, 10) > 0; + break; + case 'h': + default: + usage(argv[0], params); + exit(0); + } + } +} + +class multi_ue_sched_simulator +{ +public: + multi_ue_sched_simulator(const scheduler_expert_config& expert_cfg_, + const cell_config_builder_params& builder_params_) : + expert_cfg(expert_cfg_), + builder_params(builder_params_), + logger(srslog::fetch_basic_logger("SCHED")), + sch(create_scheduler(scheduler_config{expert_cfg, cfg_notif, metric_notif})), + next_sl_tx(builder_params.scs_common, 0) + { + du_cell_cfgs = {config_helpers::make_default_du_cell_config(builder_params)}; + du_cell_cfgs[0].pucch_cfg.f2_params.max_code_rate = max_pucch_code_rate::dot_35; + du_cell_cfgs[0].pucch_cfg.nof_csi_resources = 2; + du_cell_cfgs[0].pucch_cfg.nof_sr_resources = 2; + du_cell_cfgs[0].pucch_cfg.nof_ue_pucch_f1_res_harq = 3; + du_cell_cfgs[0].pucch_cfg.nof_ue_pucch_f2_res_harq = 6; + + sched_cell_configuration_request_message cell_cfg_msg = + test_helpers::make_default_sched_cell_configuration_request(builder_params); + + cell_cfg_msg.pucch_guardbands = config_helpers::build_pucch_guardbands_list( + du_cell_cfgs[0].pucch_cfg, cell_cfg_msg.ul_cfg_common.init_ul_bwp.generic_params.crbs.length()); + sch->handle_cell_configuration_request(cell_cfg_msg); + + du_cell_cfgs = {config_helpers::make_default_du_cell_config(builder_params)}; + du_cell_cfgs[0].pucch_cfg.f2_params.max_code_rate = max_pucch_code_rate::dot_35; + du_cell_cfgs[0].pucch_cfg.nof_csi_resources = 4; + pucch_res_mng.emplace(du_cell_cfgs); + + logger.set_context(next_sl_tx.sfn(), next_sl_tx.slot_index()); + } + + void add_ue() + { + sched_ue_creation_request_message ue_cfg_msg = test_helpers::create_default_sched_ue_creation_request( + builder_params, {lcid_t::LCID_SRB2, lcid_t::LCID_MIN_DRB}); + ue_cfg_msg.ue_index = to_du_ue_index(ue_count); + ue_cfg_msg.crnti = to_rnti(0x4601 + ue_count); + ue_cfg_msg.starts_in_fallback = false; + + auto& pcell = (*ue_cfg_msg.cfg.cells)[0]; + + // Generate PUCCH resources for the UE. + srs_du::cell_group_config cell_group; + cell_group.cells.emplace(0); + cell_group.cells[0].serv_cell_cfg = pcell.serv_cell_cfg; + bool success = pucch_res_mng->alloc_resources(cell_group); + srsran_assert(success, "Failed to allocate resources for UE={}", ue_count); + pcell.serv_cell_cfg = cell_group.cells[0].serv_cell_cfg; + + sch->handle_ue_creation_request(ue_cfg_msg); + ue_count++; + } + + void push_dl_bs(unsigned dl_bs_val) + { + for (unsigned ue_index = 0; ue_index != ue_count; ++ue_index) { + dl_buffer_state_indication_message dl_bs{}; + dl_bs.ue_index = to_du_ue_index(ue_index); + dl_bs.lcid = lcid_t::LCID_MIN_DRB; + dl_bs.bs = dl_bs_val; + sch->handle_dl_buffer_state_indication(dl_bs); + } + } + + void run_slot() { result = &sch->slot_indication(next_sl_tx, to_du_cell_index(0)); } + + void process_results() + { + slot_point result_sl_tx = next_sl_tx; + ++next_sl_tx; + + // Store previous results if any. + if (result != nullptr) { + sched_results[result_sl_tx.to_uint()] = *result; + } + + // Process past UCI results. + slot_point sl_rx = next_sl_tx - dl_pipeline_delay - uci_process_delay; + const sched_result& res = sched_results[sl_rx.to_uint()]; + if (res.success) { + uci_indication ind; + ind.slot_rx = sl_rx; + ind.cell_index = to_du_cell_index(0); + for (const auto& pucch : res.ul.pucchs) { + uci_indication::uci_pdu pdu; + pdu.ue_index = to_du_ue_index(static_cast(pucch.crnti) - 0x4601); + pdu.crnti = pucch.crnti; + if (pucch.format == pucch_format::FORMAT_1) { + uci_indication::uci_pdu::uci_pucch_f0_or_f1_pdu f1{}; + f1.sr_detected = false; + if (pucch.format_1.harq_ack_nof_bits > 0 and pucch.format_1.sr_bits == sr_nof_bits::no_sr) { + f1.ul_sinr = 10; + f1.harqs.resize(pucch.format_1.harq_ack_nof_bits, mac_harq_ack_report_status::ack); + } else if (pucch.format_1.harq_ack_nof_bits > 0) { + // ACK+SR + f1.ul_sinr = -10; + f1.harqs.resize(pucch.format_1.harq_ack_nof_bits, mac_harq_ack_report_status::dtx); + } else { + f1.ul_sinr = -10; + f1.sr_detected = false; + } + pdu.pdu = f1; + ind.ucis.push_back(pdu); + } else if (pucch.format == pucch_format::FORMAT_2) { + uci_indication::uci_pdu::uci_pucch_f2_or_f3_or_f4_pdu f2{}; + f2.ul_sinr = 10; + if (pucch.format_2.harq_ack_nof_bits > 0) { + f2.harqs.resize(pucch.format_2.harq_ack_nof_bits, mac_harq_ack_report_status::ack); + } + if (pucch.csi_rep_cfg.has_value()) { + f2.csi = csi_report_data{nullopt, + 4, + nullopt, + csi_report_pmi{csi_report_pmi::typeI_single_panel_4ports_mode1{0, nullopt, 0}}, + 15, + nullopt}; + } + pdu.pdu = f2; + ind.ucis.push_back(pdu); + } + } + for (const ul_sched_info& ul_grant : res.ul.puschs) { + if (ul_grant.uci.has_value()) { + uci_indication::uci_pdu pdu; + pdu.ue_index = to_du_ue_index(static_cast(ul_grant.pusch_cfg.rnti) - 0x4601); + pdu.crnti = ul_grant.pusch_cfg.rnti; + uci_indication::uci_pdu::uci_pusch_pdu pusch_pdu{}; + pusch_pdu.harqs.resize(ul_grant.uci->harq_ack_nof_bits, mac_harq_ack_report_status::ack); + pusch_pdu.csi = + csi_report_data{nullopt, + 4, + nullopt, + csi_report_pmi{csi_report_pmi::typeI_single_panel_4ports_mode1{0, nullopt, 0}}, + 15, + nullopt}; + ind.ucis.push_back(pdu); + } + } + + // Forward UCI indication to scheduler. + sch->handle_uci_indication(ind); + } + + logger.set_context(next_sl_tx.sfn(), next_sl_tx.slot_index()); + } + + mac_scheduler& sched() const { return *sch; } + +private: + const unsigned dl_pipeline_delay = 4; + const unsigned uci_process_delay = 2; + + sched_cfg_dummy_notifier cfg_notif; + sched_dummy_metric_notifier metric_notif; + scheduler_expert_config expert_cfg; + cell_config_builder_params builder_params; + std::vector du_cell_cfgs; + srslog::basic_logger& logger; + optional pucch_res_mng; + + std::unique_ptr sch; + + unsigned ue_count = 0; + slot_point next_sl_tx; + const sched_result* result = nullptr; + + circular_array sched_results; +}; + +void benchmark_tdd(benchmarker& bm, const bench_params& params) +{ + scheduler_expert_config sched_cfg = config_helpers::make_default_scheduler_expert_config(); + sched_cfg.ue.max_pdcch_alloc_attempts_per_slot = params.max_dl_grants_per_slot; + + cell_config_builder_params builder_params{}; + builder_params.dl_arfcn = 520002; + builder_params.band = nr_band::n41; + builder_params.channel_bw_mhz = bs_channel_bandwidth_fr1::MHz100; + builder_params.scs_common = subcarrier_spacing::kHz30; + builder_params.tdd_ul_dl_cfg_common = tdd_ul_dl_config_common{builder_params.scs_common, {10, 7, 8, 2, 0}}; + builder_params.nof_dl_ports = 4; + multi_ue_sched_simulator sim{sched_cfg, builder_params}; + + // Add UEs. + for (unsigned ue_count = 0; ue_count != params.nof_ues; ++ue_count) { + sim.add_ue(); + } + + // Update UE buffer states. + sim.push_dl_bs(params.dl_bs); + + // Run benchmark. + bm.new_measure( + fmt::format("TDD scheduling {} UEs", params.nof_ues), + 1, + [&sim]() mutable { sim.run_slot(); }, + [&]() { + sim.process_results(); + sim.push_dl_bs(params.dl_bs); + }); +} + +int main(int argc, char** argv) +{ + srslog::init(); + + bench_params params{}; + parse_args(argc, argv, params); + + srslog::fetch_basic_logger("SCHED", true) + .set_level(params.debug ? srslog::basic_levels::debug : srslog::basic_levels::warning); + + std::unique_ptr bm = std::make_unique("scheduler", params.nof_repetitions); + + benchmark_tdd(*bm, params); + + // Output results. + bm->print_percentiles_time(); +} diff --git a/tests/benchmarks/scheduler/scheduler_no_ues_benchmark.cpp b/tests/benchmarks/scheduler/scheduler_no_ues_benchmark.cpp index b5a6e056ca..06f52a2f01 100644 --- a/tests/benchmarks/scheduler/scheduler_no_ues_benchmark.cpp +++ b/tests/benchmarks/scheduler/scheduler_no_ues_benchmark.cpp @@ -21,6 +21,7 @@ */ #include "lib/scheduler/cell/cell_configuration.h" +#include "scheduler_test_doubles.h" #include "tests/unittests/scheduler/test_utils/config_generators.h" #include "srsran/du/du_cell_config_helpers.h" #include "srsran/scheduler/scheduler_factory.h" @@ -29,19 +30,6 @@ using namespace srsran; -class sched_cfg_dummy_notifier : public sched_configuration_notifier -{ -public: - void on_ue_config_complete(du_ue_index_t ue_index, bool ue_creation_result) override {} - void on_ue_delete_response(du_ue_index_t ue_index) override {} -}; - -class sched_dummy_metric_notifier final : public scheduler_ue_metrics_notifier -{ -public: - void report_metrics(span ue_metrics) override {} -}; - struct bench_params { unsigned nof_repetitions = 100; }; @@ -69,7 +57,7 @@ static void parse_args(int argc, char** argv, bench_params& params) } } -std::unique_ptr bm; +static std::unique_ptr bm; void benchmark_sib_scheduling() { diff --git a/tests/benchmarks/scheduler/scheduler_test_doubles.h b/tests/benchmarks/scheduler/scheduler_test_doubles.h new file mode 100644 index 0000000000..ec306a6097 --- /dev/null +++ b/tests/benchmarks/scheduler/scheduler_test_doubles.h @@ -0,0 +1,43 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/scheduler/scheduler_configurator.h" +#include "srsran/scheduler/scheduler_metrics.h" + +namespace srsran { + +class sched_cfg_dummy_notifier : public sched_configuration_notifier +{ +public: + void on_ue_config_complete(du_ue_index_t ue_index, bool ue_creation_result) override {} + void on_ue_delete_response(du_ue_index_t ue_index) override {} +}; + +class sched_dummy_metric_notifier final : public scheduler_ue_metrics_notifier +{ +public: + void report_metrics(span ue_metrics) override {} +}; + +} // namespace srsran diff --git a/tests/e2e/pyproject.toml b/tests/e2e/pyproject.toml index 6f46db1449..a98a36bfde 100644 --- a/tests/e2e/pyproject.toml +++ b/tests/e2e/pyproject.toml @@ -42,6 +42,7 @@ python_files = [ "tests/*.py", ] render_collapsed = true +reruns_delay = 60 [tool.black] line-length = 120 diff --git a/tests/e2e/tests/iperf.py b/tests/e2e/tests/iperf.py index 92835eb28b..988f349a33 100644 --- a/tests/e2e/tests/iperf.py +++ b/tests/e2e/tests/iperf.py @@ -31,7 +31,7 @@ TINY_DURATION = 10 SHORT_DURATION = 20 -LONG_DURATION = 5 * 60 +LONG_DURATION = 2 * 60 LOW_BITRATE = int(1e6) MEDIUM_BITRATE = int(15e6) HIGH_BITRATE = int(50e6) @@ -178,16 +178,15 @@ def test_android( @mark.parametrize( "band, common_scs, bandwidth", ( - param(78, 30, 40, id="band:%s-scs:%s-bandwidth:%s"), - param(3, 15, 20, id="band:%s-scs:%s-bandwidth:%s"), + param(78, 30, 20, id="band:%s-scs:%s-bandwidth:%s"), + param(7, 15, 20, id="band:%s-scs:%s-bandwidth:%s"), ), ) @mark.android_hp # pylint: disable=too-many-arguments -def test_android_2x2_mimo( +def test_android_hp( retina_manager: RetinaTestManager, retina_data: RetinaTestData, - reporter: None, ue_1: UEStub, fivegc: FiveGCStub, gnb: GNBStub, @@ -202,7 +201,7 @@ def test_android_2x2_mimo( """ _iperf( - reporter=reporter, + reporter=None, retina_manager=retina_manager, retina_data=retina_data, ue_array=(ue_1,), @@ -428,6 +427,13 @@ def test_zmq( param(IPerfDir.BIDIRECTIONAL, id="bidirectional", marks=mark.bidirectional), ), ) +@mark.parametrize( + "protocol", + ( + param(IPerfProto.UDP, id="udp", marks=mark.udp), + param(IPerfProto.TCP, id="tcp", marks=mark.tcp), + ), +) @mark.parametrize( "band, common_scs, bandwidth", ( @@ -437,7 +443,7 @@ def test_zmq( ) @mark.rf # pylint: disable=too-many-arguments -def test_rf_udp( +def test_rf( retina_manager: RetinaTestManager, retina_data: RetinaTestData, ue_1: UEStub, @@ -449,6 +455,7 @@ def test_rf_udp( band: int, common_scs: int, bandwidth: int, + protocol: IPerfProto, direction: IPerfDir, ): """ @@ -467,12 +474,12 @@ def test_rf_udp( bandwidth=bandwidth, sample_rate=None, # default from testbed iperf_duration=LONG_DURATION, - protocol=IPerfProto.UDP, + protocol=protocol, bitrate=MEDIUM_BITRATE, direction=direction, global_timing_advance=-1, time_alignment_calibration="auto", - always_download_artifacts=True, + always_download_artifacts=False, warning_as_errors=False, ) @@ -502,7 +509,7 @@ def _iperf( plmn: Optional[PLMN] = None, common_search_space_enable: bool = False, ): - wait_before_power_off = 2 + wait_before_power_off = 5 logging.info("Iperf Test") diff --git a/tests/e2e/tests/ping.py b/tests/e2e/tests/ping.py index ea4f893727..bf6714738b 100644 --- a/tests/e2e/tests/ping.py +++ b/tests/e2e/tests/ping.py @@ -87,13 +87,13 @@ def test_android( @mark.parametrize( "band, common_scs, bandwidth", ( - param(3, 15, 20, id="band:%s-scs:%s-bandwidth:%s"), - param(78, 30, 40, id="band:%s-scs:%s-bandwidth:%s"), + param(7, 15, 20, id="band:%s-scs:%s-bandwidth:%s"), + param(78, 30, 20, id="band:%s-scs:%s-bandwidth:%s"), ), ) @mark.android_hp # pylint: disable=too-many-arguments -def test_android_2x2_mimo( +def test_android_hp( retina_manager: RetinaTestManager, retina_data: RetinaTestData, ue_1: UEStub, diff --git a/tests/e2e/tests/steps/stub.py b/tests/e2e/tests/steps/stub.py index d727ea2a80..de565e980e 100644 --- a/tests/e2e/tests/steps/stub.py +++ b/tests/e2e/tests/steps/stub.py @@ -194,14 +194,17 @@ def ue_start_and_attach( @contextmanager def _handle_start_error(name: str) -> Generator[None, None, None]: + raise_failed = False try: yield logging.info("%s started", name) except grpc.RpcError as err: if ErrorReportedByAgent(err).code is grpc.StatusCode.ABORTED: - pytest.fail(f"{name} failed to start") + raise_failed = True else: raise err from None + if raise_failed: + pytest.fail(f"{name} failed to start") def _log_attached_ue(future: grpc.Future, ue_stub: UEStub): diff --git a/tests/integrationtests/du_high/mac_test_mode_adapter_test.cpp b/tests/integrationtests/du_high/mac_test_mode_adapter_test.cpp index 9e3e36f574..b18efa2359 100644 --- a/tests/integrationtests/du_high/mac_test_mode_adapter_test.cpp +++ b/tests/integrationtests/du_high/mac_test_mode_adapter_test.cpp @@ -83,7 +83,7 @@ class mac_dummy : public mac_interface, { return launch_no_op_task(mac_ue_delete_response{}); } - void handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override {} + bool handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override { return true; } void handle_rx_data_indication(mac_rx_data_indication pdu) override {} void handle_paging_information(const paging_information& msg) override {} async_task start() override { return launch_no_op_task(); } diff --git a/tests/integrationtests/du_high/test_utils/du_high_test_bench.h b/tests/integrationtests/du_high/test_utils/du_high_test_bench.h index c812aa545d..b46dc52945 100644 --- a/tests/integrationtests/du_high/test_utils/du_high_test_bench.h +++ b/tests/integrationtests/du_high/test_utils/du_high_test_bench.h @@ -29,7 +29,7 @@ #include "tests/test_doubles/mac/mac_test_messages.h" #include "srsran/du_high/du_high.h" #include "srsran/du_high/du_high_configuration.h" -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" namespace srsran { namespace srs_du { diff --git a/tests/integrationtests/du_high_cu/CMakeLists.txt b/tests/integrationtests/du_high_cu/CMakeLists.txt index cc4f1dd55e..c88b0ab499 100644 --- a/tests/integrationtests/du_high_cu/CMakeLists.txt +++ b/tests/integrationtests/du_high_cu/CMakeLists.txt @@ -29,6 +29,7 @@ target_link_libraries(cu_du_test srsran_cu_cp srsran_rrc srsran_support srslog + ngap_test_helpers f1ap_asn1 ngap_asn1 gtest diff --git a/tests/integrationtests/du_high_cu/cu_du_test.cpp b/tests/integrationtests/du_high_cu/cu_du_test.cpp index 480cdbfdff..90675b1e3f 100644 --- a/tests/integrationtests/du_high_cu/cu_du_test.cpp +++ b/tests/integrationtests/du_high_cu/cu_du_test.cpp @@ -32,6 +32,7 @@ #include "srsran/du/du_cell_config_helpers.h" #include "srsran/du_high/du_high_factory.h" #include "srsran/support/test_utils.h" +#include #include using namespace srsran; @@ -63,9 +64,10 @@ class cu_du_test : public ::testing::Test srs_cu_cp::dummy_ngap_amf_notifier ngap_amf_notifier; // create CU-CP config srs_cu_cp::cu_cp_configuration cu_cfg; - cu_cfg.cu_cp_executor = &workers.ctrl_exec; - cu_cfg.ngap_notifier = &ngap_amf_notifier; - cu_cfg.timers = &timers; + cu_cfg.cu_cp_executor = &workers.ctrl_exec; + cu_cfg.ngap_notifier = &ngap_amf_notifier; + cu_cfg.timers = &timers; + cu_cfg.statistics_report_period = std::chrono::seconds(1); // create and start CU cu_cp_obj = create_cu_cp(cu_cfg); diff --git a/tests/integrationtests/du_high_cu/cu_multi_du_test.cpp b/tests/integrationtests/du_high_cu/cu_multi_du_test.cpp index ae26f782fe..a20c540434 100644 --- a/tests/integrationtests/du_high_cu/cu_multi_du_test.cpp +++ b/tests/integrationtests/du_high_cu/cu_multi_du_test.cpp @@ -50,40 +50,39 @@ constexpr unsigned cu_multi_du_test::nof_dus; } // namespace -TEST_F(cu_multi_du_test, f1_setup_multiple_dus) -{ - ASSERT_EQ(this->cu_cp_inst->get_connected_dus().get_nof_dus(), 0); - - this->start_dus(); - - ASSERT_EQ(this->cu_cp_inst->get_connected_dus().get_nof_dus(), cu_multi_du_test::nof_dus); +// TEST_F(cu_multi_du_test, f1_setup_multiple_dus) +// { +// ASSERT_EQ(this->cu_cp_inst->get_connected_dus().get_nof_dus(), 0); - for (unsigned i = 0; i < cu_multi_du_test::nof_dus; i++) { - ASSERT_EQ(f1c_gw.get_last_cu_cp_rx_pdus(i).size(), 1); - ASSERT_EQ(f1c_gw.get_last_cu_cp_tx_pdus(i).size(), 1); +// this->start_dus(); - // F1 Setup sent to CU-CP. - const f1ap_message du_msg = f1c_gw.get_last_cu_cp_rx_pdus(i)[0]; - ASSERT_EQ(du_msg.pdu.type().value, asn1::f1ap::f1ap_pdu_c::types_opts::init_msg); - ASSERT_EQ(du_msg.pdu.init_msg().value.type().value, - asn1::f1ap::f1ap_elem_procs_o::init_msg_c::types_opts::f1_setup_request); +// ASSERT_EQ(this->cu_cp_inst->get_connected_dus().get_nof_dus(), cu_multi_du_test::nof_dus); - // F1 Setup Response sent back to DU. - const f1ap_message cu_msg = f1c_gw.get_last_cu_cp_tx_pdus(i)[0]; - ASSERT_EQ(cu_msg.pdu.type().value, asn1::f1ap::f1ap_pdu_c::types_opts::successful_outcome); - ASSERT_EQ(cu_msg.pdu.successful_outcome().value.type().value, - asn1::f1ap::f1ap_elem_procs_o::successful_outcome_c::types_opts::f1_setup_resp); - } -} +// for (unsigned i = 0; i < cu_multi_du_test::nof_dus; i++) { +// ASSERT_EQ(f1c_gw.get_last_cu_cp_rx_pdus(i).size(), 1); +// ASSERT_EQ(f1c_gw.get_last_cu_cp_tx_pdus(i).size(), 1); + +// // F1 Setup sent to CU-CP. +// const f1ap_message du_msg = f1c_gw.get_last_cu_cp_rx_pdus(i)[0]; +// ASSERT_EQ(du_msg.pdu.type().value, asn1::f1ap::f1ap_pdu_c::types_opts::init_msg); +// ASSERT_EQ(du_msg.pdu.init_msg().value.type().value, +// asn1::f1ap::f1ap_elem_procs_o::init_msg_c::types_opts::f1_setup_request); + +// // F1 Setup Response sent back to DU. +// const f1ap_message cu_msg = f1c_gw.get_last_cu_cp_tx_pdus(i)[0]; +// ASSERT_EQ(cu_msg.pdu.type().value, asn1::f1ap::f1ap_pdu_c::types_opts::successful_outcome); +// ASSERT_EQ(cu_msg.pdu.successful_outcome().value.type().value, +// asn1::f1ap::f1ap_elem_procs_o::successful_outcome_c::types_opts::f1_setup_resp); +// } +// } -// TODO: Fix and enable this test // TEST_F(cu_multi_du_test, multi_du_ues) // { // this->start_dus(); // // Add one UE to each DU. // for (unsigned i = 0; i < cu_multi_du_test::nof_dus; i++) { -// add_ue(i, to_rnti(0x4601)); +// ASSERT_TRUE(add_ue(i, to_rnti(0x4601))); // } // ASSERT_EQ(this->cu_cp_inst->get_connected_dus().get_nof_ues(), 2); diff --git a/tests/integrationtests/du_high_cu/du_high_cu_test_simulator.cpp b/tests/integrationtests/du_high_cu/du_high_cu_test_simulator.cpp index ecacc5a62e..74c3cb5349 100644 --- a/tests/integrationtests/du_high_cu/du_high_cu_test_simulator.cpp +++ b/tests/integrationtests/du_high_cu/du_high_cu_test_simulator.cpp @@ -24,10 +24,12 @@ #include "lib/du_high/du_high_executor_strategies.h" #include "tests/test_doubles/f1ap/f1ap_test_message_validators.h" #include "tests/test_doubles/mac/mac_test_messages.h" +#include "tests/unittests/ngap/ngap_test_messages.h" #include "srsran/cu_cp/cu_cp_factory.h" #include "srsran/du/du_cell_config_helpers.h" #include "srsran/du_high/du_high_factory.h" #include "srsran/support/test_utils.h" +#include using namespace srsran; @@ -110,13 +112,18 @@ du_high_cu_test_simulator::du_high_cu_test_simulator(const du_high_cu_cp_test_si s_nssai_t slice_cfg; slice_cfg.sst = 1; cu_cfg.ngap_config.slice_configurations.push_back(slice_cfg); + cu_cfg.statistics_report_period = std::chrono::seconds(1); // Instatiate CU-CP. cu_cp_inst = create_cu_cp(cu_cfg); + cu_cp_inst->handle_amf_connection(); // Start CU-CP. cu_cp_inst->start(); + // Connect AMF by injecting a ng_setup_response + cu_cp_inst->get_ngap_message_handler().handle_message(srs_cu_cp::generate_ng_setup_response()); + // Connect F1-C to CU-CP. f1c_gw.attach_cu_cp_du_repo(cu_cp_inst->get_connected_dus()); } diff --git a/tests/test_doubles/f1ap/f1ap_test_messages.h b/tests/test_doubles/f1ap/f1ap_test_messages.h index 4900f2ef4f..d9d8d7ee4f 100644 --- a/tests/test_doubles/f1ap/f1ap_test_messages.h +++ b/tests/test_doubles/f1ap/f1ap_test_messages.h @@ -23,7 +23,7 @@ #pragma once #include "srsran/adt/optional.h" -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/ran/lcid.h" namespace srsran { diff --git a/tests/test_doubles/mac/mac_pcap_dummy.h b/tests/test_doubles/mac/mac_pcap_dummy.h index 135e0ca304..f5f3ed0304 100644 --- a/tests/test_doubles/mac/mac_pcap_dummy.h +++ b/tests/test_doubles/mac/mac_pcap_dummy.h @@ -30,11 +30,11 @@ namespace srsran { /// Dummy pcap writer. struct mac_pcap_dummy : public mac_pcap { public: - void open(const std::string& filename_) override {} + void open(const std::string& filename_, mac_pcap_type type) override {} void close() override {} bool is_write_enabled() override { return false; } void push_pdu(mac_nr_context_info context, const_span pdu) override {} void push_pdu(mac_nr_context_info context, byte_buffer pdu) override {} }; -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/tests/unittests/adt/byte_buffer_test.cpp b/tests/unittests/adt/byte_buffer_test.cpp index 5011d0158d..7ec1abf0ef 100644 --- a/tests/unittests/adt/byte_buffer_test.cpp +++ b/tests/unittests/adt/byte_buffer_test.cpp @@ -212,7 +212,7 @@ TEST_P(two_vector_size_param_test, append) // Append two byte_buffers. byte_buffer pdu2{bytes2}; ASSERT_EQ(pdu2, bytes2); - pdu2.append(pdu); + ASSERT_TRUE(pdu2.append(pdu)); ASSERT_EQ(pdu.length() + bytes2.size(), pdu2.length()); ASSERT_EQ(pdu2.length(), pdu2.end() - pdu2.begin()); ASSERT_EQ(pdu2, concat_vec(bytes2, bytes1)); @@ -984,7 +984,7 @@ TEST(byte_buffer_writer_test, all) TESTASSERT(pdu.empty()); TESTASSERT(writer.empty()); - writer.append(5); + TESTASSERT(writer.append(5)); TESTASSERT(not pdu.empty()); TESTASSERT(not writer.empty()); TESTASSERT_EQ(1, pdu.length()); diff --git a/tests/unittests/adt/concurrent_queue_test.cpp b/tests/unittests/adt/concurrent_queue_test.cpp index da94531e14..92c545f9e6 100644 --- a/tests/unittests/adt/concurrent_queue_test.cpp +++ b/tests/unittests/adt/concurrent_queue_test.cpp @@ -61,6 +61,8 @@ using concurrent_queue_types = ::testing::Types< concurrent_queue_wait_policy::condition_variable>, concurrent_queue, concurrent_queue, + concurrent_queue, + concurrent_queue, concurrent_queue, concurrent_queue, concurrent_queue, diff --git a/tests/unittests/cu_cp/cu_cp_test.cpp b/tests/unittests/cu_cp/cu_cp_test.cpp index 050126067f..ff01eecfcd 100644 --- a/tests/unittests/cu_cp/cu_cp_test.cpp +++ b/tests/unittests/cu_cp/cu_cp_test.cpp @@ -510,6 +510,49 @@ TEST_F(cu_cp_test, when_release_command_received_then_release_command_is_sent_to ASSERT_TRUE(last_f1ap_msgs.back().pdu.init_msg().value.ue_context_release_cmd()->srb_id_present); } +TEST_F(cu_cp_test, + when_when_pdu_session_resource_setup_request_is_received_during_release_then_error_indication_is_sent) +{ + // Test preamble + du_index_t du_index = uint_to_du_index(0); + gnb_cu_ue_f1ap_id_t cu_ue_id = int_to_gnb_cu_ue_f1ap_id(0); + gnb_du_ue_f1ap_id_t du_ue_id = int_to_gnb_du_ue_f1ap_id(0); + rnti_t crnti = to_rnti(0x4601); + pci_t pci = 0; + amf_ue_id_t amf_ue_id = uint_to_amf_ue_id( + test_rgen::uniform_int(amf_ue_id_to_uint(amf_ue_id_t::min), amf_ue_id_to_uint(amf_ue_id_t::max))); + ran_ue_id_t ran_ue_id = uint_to_ran_ue_id(0); + test_preamble_ue_creation(du_index, du_ue_id, cu_ue_id, pci, crnti, amf_ue_id, ran_ue_id); + + // Inject UE Context Release Command + cu_cp_obj->get_ngap_message_handler().handle_message( + generate_valid_ue_context_release_command_with_amf_ue_ngap_id(amf_ue_id)); + + // check that the UE Context Release Command with RRC Container was sent to the DU + span last_f1ap_msgs = f1c_gw.last_tx_pdus(0); + ASSERT_FALSE(last_f1ap_msgs.empty()); + ASSERT_EQ(last_f1ap_msgs.back().pdu.type(), asn1::f1ap::f1ap_pdu_c::types_opts::options::init_msg); + ASSERT_EQ(last_f1ap_msgs.back().pdu.init_msg().value.type().value, + asn1::f1ap::f1ap_elem_procs_o::init_msg_c::types_opts::ue_context_release_cmd); + ASSERT_TRUE(last_f1ap_msgs.back().pdu.init_msg().value.ue_context_release_cmd()->rrc_container_present); + // check that the SRB ID is set if the RRC Container is included + ASSERT_TRUE(last_f1ap_msgs.back().pdu.init_msg().value.ue_context_release_cmd()->srb_id_present); + + // Inject PDU Session Resource Setup Request + cu_cp_obj->get_ngap_message_handler().handle_message( + generate_valid_pdu_session_resource_setup_request_message(amf_ue_id, ran_ue_id, uint_to_pdu_session_id(1))); + + // Inject F1AP UE Context Release Complete + cu_cp_obj->get_connected_dus() + .get_du(uint_to_du_index(0)) + .get_f1ap_message_handler() + .handle_message(generate_ue_context_release_complete(cu_ue_id, du_ue_id)); + + // check that the ErrorIndication was sent to the AMF + ASSERT_EQ(ngap_amf_notifier.last_ngap_msg.pdu.init_msg().value.type().value, + asn1::ngap::ngap_elem_procs_o::init_msg_c::types_opts::error_ind); +} + ////////////////////////////////////////////////////////////////////////////////////// /* DU Initiated UE Context Release */ ////////////////////////////////////////////////////////////////////////////////////// diff --git a/tests/unittests/cu_cp/cu_cp_test_helpers.cpp b/tests/unittests/cu_cp/cu_cp_test_helpers.cpp index a1b6dbd4b0..73634cdea6 100644 --- a/tests/unittests/cu_cp/cu_cp_test_helpers.cpp +++ b/tests/unittests/cu_cp/cu_cp_test_helpers.cpp @@ -61,6 +61,9 @@ cu_cp_test::cu_cp_test() // UE config cfg.ue_config.inactivity_timer = std::chrono::seconds{7200}; + // periodic statistic logging + cfg.statistics_report_period = std::chrono::seconds(1); + // create and start CU-CP. cu_cp_obj = std::make_unique(std::move(cfg)); cu_cp_obj->handle_amf_connection(); @@ -158,15 +161,21 @@ void cu_cp_test::test_preamble_all_connected(du_index_t du_index, pci_t pci) ASSERT_EQ(f1c_gw.nof_connections(), 1U); ASSERT_EQ(cu_cp_obj->get_connected_dus().get_nof_dus(), 1U); + // Create a new CU-UP connection to the CU-CP, creating a new CU-UP processor in the CU-CP in the process. e1ap_gw.request_new_cu_up_connection(); ASSERT_EQ(e1ap_gw.nof_connections(), 1U); ASSERT_EQ(cu_cp_obj->get_connected_cu_ups().get_nof_cu_ups(), 1U); - // Generate F1SetupRequest + // Pass F1SetupRequest to the CU-CP f1ap_message f1setup_msg = generate_f1_setup_request(0x11, 6576, pci); - - // Pass message to CU-CP cu_cp_obj->get_connected_dus().get_du(du_index).get_f1ap_message_handler().handle_message(f1setup_msg); + + // Pass E1SetupRequest to the CU-CP + e1ap_message e1setup_msg = generate_valid_cu_up_e1_setup_request(); + cu_cp_obj->get_connected_cu_ups() + .get_cu_up(uint_to_cu_up_index(0)) + .get_e1ap_message_handler() + .handle_message(e1setup_msg); } void cu_cp_test::test_preamble_ue_creation(du_index_t du_index, diff --git a/tests/unittests/cu_cp/du_processor/du_processor_routine_manager_test_helpers.cpp b/tests/unittests/cu_cp/du_processor/du_processor_routine_manager_test_helpers.cpp index 9ede699ecb..a07f664dc6 100644 --- a/tests/unittests/cu_cp/du_processor/du_processor_routine_manager_test_helpers.cpp +++ b/tests/unittests/cu_cp/du_processor/du_processor_routine_manager_test_helpers.cpp @@ -22,6 +22,7 @@ #include "du_processor_routine_manager_test_helpers.h" #include "tests/unittests/rrc/rrc_ue_test_messages.h" +#include using namespace srsran; using namespace srs_cu_cp; @@ -32,7 +33,8 @@ du_processor_routine_manager_test::du_processor_routine_manager_test() cu_cp_logger.set_level(srslog::basic_levels::debug); srslog::init(); - ue_task_sched = std::make_unique(timers, ctrl_worker); + ue_task_sched = std::make_unique(timers, ctrl_worker); + cu_cp_notifier = std::make_unique(ngap_ue_removal_handler, &ue_mng); drb_cfg = {}; drb_cfg.five_qi_config[uint_to_five_qi(9)] = {}; diff --git a/tests/unittests/cu_cp/du_processor/du_processor_routine_manager_test_helpers.h b/tests/unittests/cu_cp/du_processor/du_processor_routine_manager_test_helpers.h index e19f9467f0..d995aecb75 100644 --- a/tests/unittests/cu_cp/du_processor/du_processor_routine_manager_test_helpers.h +++ b/tests/unittests/cu_cp/du_processor/du_processor_routine_manager_test_helpers.h @@ -57,7 +57,8 @@ class du_processor_routine_manager_test : public ::testing::Test timer_manager timers; dummy_du_processor_rrc_ue_control_message_notifier rrc_ue_ctrl_notifier; dummy_du_processor_rrc_ue_srb_control_notifier rrc_ue_srb_ctrl_notifier; - dummy_du_processor_ue_handler du_proc_ue_handler; + dummy_ngap_ue_context_removal_handler ngap_ue_removal_handler; + std::unique_ptr cu_cp_notifier; std::unique_ptr rrc_ue_up_resource_manager; std::unique_ptr routine_mng; }; diff --git a/tests/unittests/cu_cp/du_processor/du_processor_test.cpp b/tests/unittests/cu_cp/du_processor/du_processor_test.cpp index 15e3680105..f738c34b2f 100644 --- a/tests/unittests/cu_cp/du_processor/du_processor_test.cpp +++ b/tests/unittests/cu_cp/du_processor/du_processor_test.cpp @@ -118,7 +118,7 @@ TEST_F(du_processor_test, when_ue_creation_msg_valid_then_ue_added) ASSERT_EQ(du_processor_obj->get_nof_ues(), 1); } -TEST_F(du_processor_test, when_cell_id_invalid_then_ue_not_added) +TEST_F(du_processor_test, when_cell_id_invalid_then_ue_creation_fails) { // Generate valid F1SetupRequest f1ap_f1_setup_request f1_setup_request; @@ -134,28 +134,6 @@ TEST_F(du_processor_test, when_cell_id_invalid_then_ue_not_added) // Pass message to DU processor ue_creation_complete_message ue_creation_complete_msg = du_processor_obj->handle_ue_creation_request(ue_creation_msg); ASSERT_EQ(ue_creation_complete_msg.ue_index, ue_index_t::invalid); - - ASSERT_EQ(du_processor_obj->get_nof_ues(), 0); -} - -TEST_F(du_processor_test, when_rnti_invalid_then_ue_not_added) -{ - // Generate valid F1SetupRequest - f1ap_f1_setup_request f1_setup_request; - generate_valid_f1_setup_request(f1_setup_request); - - // Pass message to DU processor - du_processor_obj->handle_f1_setup_request(f1_setup_request); - - // Generate ue_creation message - ue_index_t ue_index = du_processor_obj->get_du_processor_f1ap_interface().get_new_ue_index(); - cu_cp_ue_creation_message ue_creation_msg = generate_ue_creation_message(ue_index, INVALID_RNTI, 6576); - - // Pass message to DU processor - ue_creation_complete_message ue_creation_complete_msg = du_processor_obj->handle_ue_creation_request(ue_creation_msg); - ASSERT_EQ(ue_creation_complete_msg.ue_index, ue_index_t::invalid); - - ASSERT_EQ(du_processor_obj->get_nof_ues(), 0); } TEST_F(du_processor_test, when_ue_exists_then_ue_not_added) @@ -218,15 +196,9 @@ TEST_F(du_processor_test, when_max_nof_ues_exceeded_then_ue_not_added) ASSERT_EQ(du_processor_obj->get_nof_ues(), MAX_NOF_UES_PER_DU); - // Add one more UE to DU processor - // Generate ue_creation message - rnti_t c_rnti = to_rnti(MAX_NOF_UES_PER_DU + 1); - ue_index_t ue_index = du_processor_obj->get_du_processor_f1ap_interface().get_new_ue_index(); - cu_cp_ue_creation_message ue_creation_msg = generate_ue_creation_message(ue_index, c_rnti, 6576); - - // Pass message to DU processor - ue_creation_complete_message ue_creation_complete_msg = du_processor_obj->handle_ue_creation_request(ue_creation_msg); - ASSERT_EQ(ue_creation_complete_msg.ue_index, ue_index_t::invalid); + // Try to allocate additional UE index + ue_index_t ue_index = du_processor_obj->get_du_processor_f1ap_interface().get_new_ue_index(); + ASSERT_EQ(ue_index, ue_index_t::invalid); ASSERT_EQ(du_processor_obj->get_nof_ues(), MAX_NOF_UES_PER_DU); } @@ -254,7 +226,7 @@ TEST_F(du_processor_test, when_ue_context_release_command_received_then_ue_delet ASSERT_EQ(du_processor_obj->get_nof_ues(), 1); // Generate UE context release command message - rrc_ue_context_release_command ue_context_release_command = generate_ue_context_release_command(ue_index); + cu_cp_ue_context_release_command ue_context_release_command = generate_ue_context_release_command(ue_index); // Pass message to DU processor t = du_processor_obj->get_du_processor_rrc_ue_interface().handle_ue_context_release_command( @@ -279,6 +251,8 @@ TEST_F(du_processor_test, when_valid_ue_creation_request_received_after_ue_was_r // Reduce logger loglevel to warning to reduce console output srslog::fetch_basic_logger("CU-CP").set_level(srslog::basic_levels::warning); srslog::fetch_basic_logger("CU-UEMNG").set_level(srslog::basic_levels::warning); + srslog::fetch_basic_logger("RRC").set_level(srslog::basic_levels::warning); + srslog::fetch_basic_logger("PDCP").set_level(srslog::basic_levels::warning); srslog::fetch_basic_logger("TEST").set_level(srslog::basic_levels::warning); // Add the maximum number of UEs @@ -292,17 +266,26 @@ TEST_F(du_processor_test, when_valid_ue_creation_request_received_after_ue_was_r ue_creation_complete_message ue_creation_complete_msg = du_processor_obj->handle_ue_creation_request(ue_creation_msg); ASSERT_NE(ue_creation_complete_msg.ue_index, ue_index_t::invalid); + + // create SRB1 + srb_creation_message srb1_msg{}; + srb1_msg.ue_index = ue_index; + srb1_msg.srb_id = srb_id_t::srb1; + srb1_msg.pdcp_cfg = asn1::rrc_nr::pdcp_cfg_s{}; + ue_mng.find_du_ue(ue_index)->get_rrc_ue_srb_notifier().create_srb(srb1_msg); } // Reset logger loglevel srslog::fetch_basic_logger("CU-CP").set_level(srslog::basic_levels::debug); srslog::fetch_basic_logger("CU-UEMNG").set_level(srslog::basic_levels::debug); + srslog::fetch_basic_logger("RRC").set_level(srslog::basic_levels::debug); + srslog::fetch_basic_logger("PDCP").set_level(srslog::basic_levels::debug); srslog::fetch_basic_logger("TEST").set_level(srslog::basic_levels::debug); ASSERT_EQ(du_processor_obj->get_nof_ues(), MAX_NOF_UES_PER_DU); // Generate UE context release command message - rrc_ue_context_release_command ue_context_release_command = generate_ue_context_release_command(ue_index_t::min); + cu_cp_ue_context_release_command ue_context_release_command = generate_ue_context_release_command(ue_index_t::min); // Pass message to DU processor t = du_processor_obj->get_du_processor_rrc_ue_interface().handle_ue_context_release_command( diff --git a/tests/unittests/cu_cp/du_processor/du_processor_test_helpers.cpp b/tests/unittests/cu_cp/du_processor/du_processor_test_helpers.cpp index 200e43cd6e..26d9c84eac 100644 --- a/tests/unittests/cu_cp/du_processor/du_processor_test_helpers.cpp +++ b/tests/unittests/cu_cp/du_processor/du_processor_test_helpers.cpp @@ -21,8 +21,10 @@ */ #include "du_processor_test_helpers.h" +#include "tests/unittests/cu_cp/test_helpers.h" #include "srsran/cu_cp/cell_meas_manager.h" #include "srsran/support/async/async_test_utils.h" +#include using namespace srsran; using namespace srs_cu_cp; @@ -33,6 +35,8 @@ du_processor_test::du_processor_test() cu_cp_logger.set_level(srslog::basic_levels::debug); srslog::init(); + cu_cp_notifier = std::make_unique(ngap_ue_removal_handler, &ue_mng); + // create ue task scheduler ue_task_sched = std::make_unique(timers, ctrl_worker); @@ -41,11 +45,12 @@ du_processor_test::du_processor_test() du_cfg.du_index = uint_to_du_index(0); du_processor_obj = create_du_processor(std::move(du_cfg), - cu_cp_notifier, + *cu_cp_notifier, f1ap_du_mgmt_notifier, f1ap_pdu_notifier, e1ap_ctrl_notifier, ngap_ctrl_notifier, + f1ap_cu_cp_notifier, rrc_ue_ngap_notifier, rrc_ue_ngap_notifier, rrc_ue_cu_cp_notifier, diff --git a/tests/unittests/cu_cp/du_processor/du_processor_test_helpers.h b/tests/unittests/cu_cp/du_processor/du_processor_test_helpers.h index 664ee8a5ec..66e32d9f1f 100644 --- a/tests/unittests/cu_cp/du_processor/du_processor_test_helpers.h +++ b/tests/unittests/cu_cp/du_processor/du_processor_test_helpers.h @@ -60,11 +60,13 @@ class du_processor_test : public ::testing::Test up_resource_manager_cfg up_config; ue_manager ue_mng{ue_config, up_config}; dummy_cell_meas_manager cell_meas_mng; - dummy_du_processor_cu_cp_notifier cu_cp_notifier; + dummy_ngap_ue_context_removal_handler ngap_ue_removal_handler; + std::unique_ptr cu_cp_notifier; dummy_f1ap_pdu_notifier f1ap_pdu_notifier; dummy_f1ap_du_management_notifier f1ap_du_mgmt_notifier; dummy_du_processor_e1ap_control_notifier e1ap_ctrl_notifier; dummy_du_processor_ngap_control_notifier ngap_ctrl_notifier; + dummy_f1ap_ue_removal_notifier f1ap_cu_cp_notifier; dummy_rrc_ue_ngap_adapter rrc_ue_ngap_notifier; dummy_rrc_ue_cu_cp_adapter rrc_ue_cu_cp_notifier; std::unique_ptr ue_task_sched; diff --git a/tests/unittests/cu_cp/du_processor/ue_context_release_routine_test.cpp b/tests/unittests/cu_cp/du_processor/ue_context_release_routine_test.cpp index 672affa543..c198f27a56 100644 --- a/tests/unittests/cu_cp/du_processor/ue_context_release_routine_test.cpp +++ b/tests/unittests/cu_cp/du_processor/ue_context_release_routine_test.cpp @@ -31,14 +31,14 @@ using namespace srs_cu_cp; class ue_context_release_test : public du_processor_routine_manager_test { protected: - void start_procedure(const rrc_ue_context_release_command& msg, - ue_context_outcome_t ue_context_modification_outcome, - bearer_context_outcome_t bearer_context_modification_outcome) + void start_procedure(const cu_cp_ue_context_release_command& msg, + ue_context_outcome_t ue_context_modification_outcome, + bearer_context_outcome_t bearer_context_modification_outcome) { f1ap_ue_ctxt_notifier.set_ue_context_modification_outcome(ue_context_modification_outcome); e1ap_ctrl_notifier.set_second_message_outcome(bearer_context_modification_outcome); - t = routine_mng->start_ue_context_release_routine(msg, du_proc_ue_handler, *ue_task_sched.get()); + t = routine_mng->start_ue_context_release_routine(msg, *cu_cp_notifier); t_launcher.emplace(t); } @@ -54,7 +54,7 @@ class ue_context_release_test : public du_processor_routine_manager_test return ue_index; } - bool was_ue_context_release_successful() const + bool was_ue_context_release_successful() { if (not t.ready()) { return false; @@ -64,6 +64,10 @@ class ue_context_release_test : public du_processor_routine_manager_test return false; } + if (ue_mng.get_nof_ues() != 0) { + return false; + } + return true; } @@ -77,9 +81,9 @@ TEST_F(ue_context_release_test, when_ue_context_release_command_received_then_re ue_index_t ue_index = add_ue(MIN_PCI, MIN_CRNTI); // Generate UE context release command message - rrc_ue_context_release_command ue_context_release_command = generate_ue_context_release_command(ue_index); + cu_cp_ue_context_release_command ue_context_release_command = generate_ue_context_release_command(ue_index); this->start_procedure(ue_context_release_command, {true}, {true}); - // nothing has failed to be release + // nothing has failed to be released ASSERT_TRUE(was_ue_context_release_successful()); } diff --git a/tests/unittests/cu_cp/du_processor_test_messages.cpp b/tests/unittests/cu_cp/du_processor_test_messages.cpp index d632ccc92c..811d8ab9f8 100644 --- a/tests/unittests/cu_cp/du_processor_test_messages.cpp +++ b/tests/unittests/cu_cp/du_processor_test_messages.cpp @@ -86,11 +86,11 @@ srsran::srs_cu_cp::generate_ue_creation_message(ue_index_t ue_index, rnti_t c_rn return ue_creation_msg; } -rrc_ue_context_release_command srsran::srs_cu_cp::generate_ue_context_release_command(ue_index_t ue_index) +cu_cp_ue_context_release_command srsran::srs_cu_cp::generate_ue_context_release_command(ue_index_t ue_index) { - rrc_ue_context_release_command ue_context_release_command = {}; - ue_context_release_command.ue_index = ue_index; - ue_context_release_command.cause = cause_radio_network_t::unspecified; + cu_cp_ue_context_release_command ue_context_release_command = {}; + ue_context_release_command.ue_index = ue_index; + ue_context_release_command.cause = cause_radio_network_t::unspecified; return ue_context_release_command; } diff --git a/tests/unittests/cu_cp/du_processor_test_messages.h b/tests/unittests/cu_cp/du_processor_test_messages.h index 66040226e0..297088747b 100644 --- a/tests/unittests/cu_cp/du_processor_test_messages.h +++ b/tests/unittests/cu_cp/du_processor_test_messages.h @@ -51,7 +51,7 @@ cu_cp_ue_creation_message generate_ue_creation_message(ue_index_t ue_index, rnti /// \brief Generate a dummy UE Context Release Command. /// \param[in] ue_index The UE Index to use. /// \return The dummy UE Context Release Command. -rrc_ue_context_release_command generate_ue_context_release_command(ue_index_t ue_index); +cu_cp_ue_context_release_command generate_ue_context_release_command(ue_index_t ue_index); /// \brief Generate a dummy PDU Session Resource Setup request. cu_cp_pdu_session_resource_setup_request generate_pdu_session_resource_setup(unsigned num_pdu_sessions = 1, diff --git a/tests/unittests/cu_cp/mobility/mobility_test_helpers.cpp b/tests/unittests/cu_cp/mobility/mobility_test_helpers.cpp index 3c0cc31afb..deef6adc4f 100644 --- a/tests/unittests/cu_cp/mobility/mobility_test_helpers.cpp +++ b/tests/unittests/cu_cp/mobility/mobility_test_helpers.cpp @@ -43,7 +43,7 @@ mobility_test::~mobility_test() du_wrapper* mobility_test::create_du(const du_processor_config_t& du_cfg) { - auto it = du_db.emplace(du_cfg.du_index, du_wrapper{}); + auto it = du_db.emplace(du_cfg.du_index, du_wrapper{ue_mng}); du_wrapper& new_du = it.first->second; // create ue task scheduler @@ -56,6 +56,7 @@ du_wrapper* mobility_test::create_du(const du_processor_config_t& du_cfg) new_du.f1ap_pdu_notifier, new_du.e1ap_ctrl_notifier, new_du.ngap_ctrl_notifier, + new_du.f1ap_cu_cp_notifier, new_du.rrc_ue_ngap_notifier, new_du.rrc_ue_ngap_notifier, new_du.rrc_ue_cu_cp_notifier, diff --git a/tests/unittests/cu_cp/mobility/mobility_test_helpers.h b/tests/unittests/cu_cp/mobility/mobility_test_helpers.h index 4c6c20da6b..efa99d30cd 100644 --- a/tests/unittests/cu_cp/mobility/mobility_test_helpers.h +++ b/tests/unittests/cu_cp/mobility/mobility_test_helpers.h @@ -77,14 +77,16 @@ class f1ap_mobility_test_adapter : public f1ap_message_handler }; struct du_wrapper { - du_wrapper() : cu_cp_notifier(nullptr), f1ap_pdu_notifier(nullptr){}; + du_wrapper(ue_manager& ue_mng) : cu_cp_notifier(ngap_ue_removal_handler, &ue_mng), f1ap_pdu_notifier(nullptr){}; dummy_cell_meas_manager cell_meas_mng; + dummy_ngap_ue_context_removal_handler ngap_ue_removal_handler; dummy_du_processor_cu_cp_notifier cu_cp_notifier; dummy_f1ap_pdu_notifier f1ap_pdu_notifier; dummy_f1ap_du_management_notifier f1ap_du_mgmt_notifier; dummy_du_processor_e1ap_control_notifier e1ap_ctrl_notifier; dummy_du_processor_ngap_control_notifier ngap_ctrl_notifier; + dummy_f1ap_ue_removal_notifier f1ap_cu_cp_notifier; dummy_rrc_ue_ngap_adapter rrc_ue_ngap_notifier; dummy_rrc_ue_cu_cp_adapter rrc_ue_cu_cp_notifier; std::unique_ptr ue_task_sched; diff --git a/tests/unittests/cu_cp/test_helpers.h b/tests/unittests/cu_cp/test_helpers.h index 9b34d84f7e..967ab29604 100644 --- a/tests/unittests/cu_cp/test_helpers.h +++ b/tests/unittests/cu_cp/test_helpers.h @@ -31,11 +31,12 @@ #include "srsran/cu_cp/cu_cp_types.h" #include "srsran/cu_cp/cu_up_processor.h" #include "srsran/cu_cp/du_processor.h" -#include "srsran/support/async/async_task_loop.h" #include "srsran/support/async/async_test_utils.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/test_utils.h" #include #include +#include namespace srsran { namespace srs_cu_cp { @@ -66,34 +67,82 @@ struct dummy_du_processor_ue_task_scheduler : public du_processor_ue_task_schedu void tick_timer() { timer_db.tick(); } private: - async_task_sequencer ctrl_loop{16}; - timer_manager& timer_db; - task_executor& exec; + fifo_async_task_scheduler ctrl_loop{16}; + timer_manager& timer_db; + task_executor& exec; }; struct dummy_du_processor_cu_cp_notifier : public du_processor_cu_cp_notifier { public: - dummy_ngap_du_processor_notifier ngap_notifier; + explicit dummy_du_processor_cu_cp_notifier(ngap_ue_context_removal_handler& ngap_handler_, + ue_manager* ue_mng_ = nullptr) : + ngap_notifier(std::make_unique(ngap_handler_)), ue_mng(ue_mng_) + { + } - explicit dummy_du_processor_cu_cp_notifier(cu_cp_du_event_handler* cu_cp_handler_ = nullptr) : - cu_cp_handler(cu_cp_handler_) + void attach_handler(cu_cp_du_event_handler* cu_cp_handler_, cu_cp_ue_removal_handler* ue_removal_handler_) { + cu_cp_handler = cu_cp_handler_; + ue_removal_handler = ue_removal_handler_; } - void attach_handler(cu_cp_du_event_handler* cu_cp_handler_) { cu_cp_handler = cu_cp_handler_; } + void on_du_processor_created(du_index_t du_index, + f1ap_ue_context_removal_handler& f1ap_handler, + f1ap_statistics_handler& f1ap_statistic_handler, + rrc_ue_removal_handler& rrc_handler, + rrc_du_statistics_handler& rrc_statistic_handler) override + { + logger.info("du={}: Received a DU Processor creation notification", du_index); + + if (cu_cp_handler != nullptr) { + cu_cp_handler->handle_du_processor_creation( + du_index, f1ap_handler, f1ap_statistic_handler, rrc_handler, rrc_statistic_handler); + } else { + rrc_removal_handler = &rrc_handler; + } + } - void on_rrc_ue_created(du_index_t du_index, ue_index_t ue_index, rrc_ue_interface& rrc_ue) override + void on_rrc_ue_created(ue_index_t ue_index, rrc_ue_interface& rrc_ue) override { - logger.info("Received a RRC UE creation notification"); + logger.info("ue={}: Received a RRC UE creation notification", ue_index); if (cu_cp_handler != nullptr) { - cu_cp_handler->handle_rrc_ue_creation(du_index, ue_index, rrc_ue, ngap_notifier); + cu_cp_handler->handle_rrc_ue_creation(ue_index, rrc_ue, *ngap_notifier); + } + } + + void on_ue_removal_required(ue_index_t ue_index) override + { + logger.info("ue={}: Received a UE removal request", ue_index); + + if (ue_removal_handler != nullptr) { + ue_removal_handler->handle_ue_removal_request(ue_index); + } else { + if (ue_mng != nullptr) { + ue_mng->remove_ue(ue_index); + } + + if (rrc_removal_handler != nullptr) { + rrc_removal_handler->remove_ue(ue_index); + } } } private: - srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST"); - cu_cp_du_event_handler* cu_cp_handler = nullptr; + srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST"); + std::unique_ptr ngap_notifier; + ue_manager* ue_mng = nullptr; + cu_cp_du_event_handler* cu_cp_handler = nullptr; + cu_cp_ue_removal_handler* ue_removal_handler = nullptr; + rrc_ue_removal_handler* rrc_removal_handler = nullptr; +}; + +struct dummy_ngap_ue_context_removal_handler : public ngap_ue_context_removal_handler { +public: + void remove_ue_context(ue_index_t ue_index) override { logger.info("ue={}: Removing UE", ue_index); } + +private: + srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST"); }; // Configuration struct to parameterize the modification outcome @@ -372,6 +421,8 @@ struct dummy_du_processor_f1ap_ue_context_notifier : public du_processor_f1ap_ue }); } + bool on_intra_du_reestablishment(ue_index_t ue_index, ue_index_t old_ue_index) override { return true; } + const f1ap_ue_context_modification_request& get_ctxt_mod_request() { return ue_context_modifcation_request; } private: @@ -513,8 +564,6 @@ struct dummy_du_processor_rrc_du_ue_notifier : public du_processor_rrc_du_ue_not return nullptr; } - void on_ue_context_release_command(ue_index_t ue_index) override { logger.info("Received a UE Release Command"); } - /// Send RRC Release to all UEs connected to this DU. void on_release_ues() override { logger.info("Releasing all UEs"); } @@ -540,26 +589,5 @@ struct dummy_cu_up_processor_cu_up_management_notifier : public cu_up_processor_ srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST"); }; -struct dummy_du_processor_ue_handler : public du_processor_ue_handler { -public: - dummy_du_processor_ue_handler() = default; - - async_task remove_ue(ue_index_t ue_index) override - { - logger.info("Removing ue={}", ue_index); - last_ue_index = ue_index; - - return launch_async([](coro_context>& ctx) mutable { - CORO_BEGIN(ctx); - CORO_RETURN(); - }); - } - - ue_index_t last_ue_index = ue_index_t::invalid; - -private: - srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST"); -}; - } // namespace srs_cu_cp } // namespace srsran diff --git a/tests/unittests/cu_cp/ue_manager/ue_manager_test.cpp b/tests/unittests/cu_cp/ue_manager/ue_manager_test.cpp index 0a66a97ad3..c9e3a0ce76 100644 --- a/tests/unittests/cu_cp/ue_manager/ue_manager_test.cpp +++ b/tests/unittests/cu_cp/ue_manager/ue_manager_test.cpp @@ -68,20 +68,6 @@ TEST_F(ue_manager_test, when_more_than_max_ue_indexes_allocated_then_ue_index_in ASSERT_EQ(ue_mng.allocate_new_ue_index(du_index), ue_index_t::invalid); } -/// Test creation of a DU UE with an invalid RNTI -TEST_F(ue_manager_test, when_rnti_invalid_then_ue_not_created) -{ - du_index_t du_index = uint_to_du_index(0); - ue_index_t ue_index = ue_mng.allocate_new_ue_index(du_index); - rnti_t rnti = rnti_t::INVALID_RNTI; - - auto* ue = ue_mng.add_ue(ue_index, MIN_PCI, rnti); - - // check that the UE has not been added - ASSERT_EQ(ue, nullptr); - ASSERT_EQ(ue_mng.get_nof_du_ues(du_index), 0U); -} - /// Test successful creation of a DU UE TEST_F(ue_manager_test, when_rnti_valid_then_ue_added) { @@ -155,7 +141,7 @@ TEST_F(ue_manager_test, when_ue_exists_then_removal_successful) auto* ue = ue_mng.add_ue(ue_index, MIN_PCI, rnti); - ue_mng.remove_du_ue(ue->get_ue_index()); + ue_mng.remove_ue(ue->get_ue_index()); // check that the UE has been removed ASSERT_EQ(ue_mng.get_nof_du_ues(du_index), 0U); @@ -175,17 +161,14 @@ TEST_F(ue_manager_test, when_ngap_ue_context_exists_then_du_ue_removal_successfu // add a NGAP context auto* ue_ngap = - ue_mng.add_ue(ue->get_ue_index(), rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + ue_mng.add_ue(ue->get_ue_index(), rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); // check that the UE has been created ASSERT_NE(ue_ngap, nullptr); - ue_mng.remove_du_ue(ue->get_ue_index()); + ue_mng.remove_ue(ue->get_ue_index()); // check that the UE has been removed - ASSERT_EQ(ue_mng.get_nof_du_ues(du_index), 0U); - - // NGAP context has been added, so the UE should not be completely removed - ASSERT_EQ(ue_mng.get_nof_ngap_ues(), 1U); + ASSERT_EQ(ue_mng.get_nof_ues(), 0U); } /// Test creation of multiple DU UEs @@ -271,10 +254,9 @@ TEST_F(ue_manager_test, when_more_than_max_ues_added_then_ue_not_created) ASSERT_EQ(ue_mng.get_nof_du_ues(du_index), MAX_NOF_UES_PER_DU); ue_index_t ue_index = ue_mng.allocate_new_ue_index(du_index); - auto* ue = ue_mng.add_ue(ue_index, MIN_PCI, rnti_t::MAX_CRNTI); + ASSERT_EQ(ue_index, ue_index_t::invalid); // check that the UE has not been added - ASSERT_EQ(ue, nullptr); ASSERT_EQ(ue_mng.get_nof_du_ues(du_index), MAX_NOF_UES_PER_DU); } @@ -285,7 +267,7 @@ TEST_F(ue_manager_test, when_more_than_max_ues_added_then_ue_not_created) /// Test creation of NGAP UE before creation of a DU UE TEST_F(ue_manager_test, when_ue_not_created_then_ngap_ue_not_added) { - auto* ue = ue_mng.add_ue(uint_to_ue_index(0), rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + auto* ue = ue_mng.add_ue(uint_to_ue_index(0), rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); // check that the UE has not been added ASSERT_EQ(ue, nullptr); @@ -297,7 +279,7 @@ TEST_F(ue_manager_test, when_ue_created_then_then_ngap_ue_added) { ue_index_t ue_index = create_ue(uint_to_du_index(0), MIN_PCI, to_rnti(0x4601)); - auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); // check that the NGAP UE has been added ASSERT_NE(ue, nullptr); @@ -306,12 +288,6 @@ TEST_F(ue_manager_test, when_ue_created_then_then_ngap_ue_added) // check that the UE index is valid ASSERT_NE(ue->get_ue_index(), ue_index_t::invalid); - // check that a RAN UE ID has been set - ASSERT_NE(ue->get_ran_ue_id(), ran_ue_id_t::invalid); - - // check that the lookup by RAN UE ID works - ASSERT_EQ(ue->get_ue_index(), ue_mng.get_ue_index(ue->get_ran_ue_id())); - // check that the number of NGAP UEs is 1 ASSERT_EQ(ue_mng.get_nof_ngap_ues(), 1U); } @@ -321,7 +297,7 @@ TEST_F(ue_manager_test, when_ue_index_invalid_then_ngap_ue_not_found) { ue_index_t ue_index = create_ue(uint_to_du_index(0), MIN_PCI, to_rnti(0x4601)); - auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); // check that the NGAP UE has been created ASSERT_NE(ue, nullptr); @@ -335,7 +311,7 @@ TEST_F(ue_manager_test, when_ngap_context_already_exits_then_ue_not_added) { ue_index_t ue_index = create_ue(uint_to_du_index(0), MIN_PCI, to_rnti(0x4601)); - auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); // check that the NGAP UE has been created ASSERT_NE(ue, nullptr); @@ -343,7 +319,7 @@ TEST_F(ue_manager_test, when_ngap_context_already_exits_then_ue_not_added) // check that the number of NGAP UEs is 1 ASSERT_EQ(ue_mng.get_nof_ngap_ues(), 1U); - auto* ue2 = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + auto* ue2 = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); // check that the UE has not been added ASSERT_EQ(ue2, nullptr); @@ -356,15 +332,12 @@ TEST_F(ue_manager_test, when_du_ue_context_exists_then_ngap_ue_removal_successfu du_index_t du_index = uint_to_du_index(0); ue_index_t ue_index = create_ue(du_index, MIN_PCI, to_rnti(0x4601)); - auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); - ue_mng.remove_ngap_ue(ue->get_ue_index()); + ue_mng.remove_ue(ue->get_ue_index()); - // check that the NGAP UE has been removed - ASSERT_EQ(ue_mng.get_nof_ngap_ues(), 0U); - - // check that the DU UE has not been removed - ASSERT_EQ(ue_mng.get_nof_du_ues(du_index), 1U); + // check that the UE has been removed + ASSERT_EQ(ue_mng.get_nof_ues(), 0U); } /// Test successful removal of a NGAP UE @@ -374,15 +347,13 @@ TEST_F(ue_manager_test, when_ngap_ue_exists_then_removal_successful) ue_index_t ue_index = create_ue(du_index, MIN_PCI, to_rnti(0x4601)); - auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); - ue_mng.remove_du_ue(ue->get_ue_index()); + ue_mng.remove_ue(ue->get_ue_index()); // check that the DU UE has been removed ASSERT_EQ(ue_mng.get_nof_du_ues(du_index), 0U); - ue_mng.remove_ngap_ue(ue->get_ue_index()); - // check that the NGAP UE has been removed ASSERT_EQ(ue_mng.get_nof_ngap_ues(), 0U); } @@ -399,7 +370,7 @@ TEST_F(ue_manager_test, when_multiple_ngap_ues_added_then_ues_exist) for (unsigned it = rnti_t::MIN_CRNTI; it < rnti_t::MIN_CRNTI + MAX_NOF_UES_PER_DU; it++) { ue_index_t ue_index = create_ue(uint_to_du_index(du_idx), MIN_PCI, to_rnti(du_offset + it)); - auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); // check that the UE has been created ASSERT_NE(ue, nullptr); @@ -408,12 +379,6 @@ TEST_F(ue_manager_test, when_multiple_ngap_ues_added_then_ues_exist) // check that the UE index is valid ASSERT_NE(ue->get_ue_index(), ue_index_t::invalid); - // check that the RAN UE ID has been set - ASSERT_NE(ue->get_ran_ue_id(), ran_ue_id_t::invalid); - - // check that the lookup by RAN UE ID works - ASSERT_EQ(ue->get_ue_index(), ue_mng.get_ue_index(ue->get_ran_ue_id())); - // check that the number of NGAP UEs is increased ASSERT_EQ(ue_mng.get_nof_ngap_ues(), du_offset + it - rnti_t::MIN_CRNTI + 1); } @@ -439,7 +404,7 @@ TEST_F(ue_manager_test, when_more_than_max_ues_added_then_ngap_ue_not_created) for (unsigned it = rnti_t::MIN_CRNTI; it < rnti_t::MIN_CRNTI + MAX_NOF_UES_PER_DU; it++) { ue_index_t ue_index = create_ue(uint_to_du_index(du_idx), MIN_PCI, to_rnti(du_offset + it)); - auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); + auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, *du_processor_ctrl_notifier); // check that the UE has been created ASSERT_NE(ue, nullptr); @@ -448,12 +413,6 @@ TEST_F(ue_manager_test, when_more_than_max_ues_added_then_ngap_ue_not_created) // check that the UE index is valid ASSERT_NE(ue->get_ue_index(), ue_index_t::invalid); - // check that the RAN UE ID has been set - ASSERT_NE(ue->get_ran_ue_id(), ran_ue_id_t::invalid); - - // check that the lookup by RAN UE ID works - ASSERT_EQ(ue->get_ue_index(), ue_mng.get_ue_index(ue->get_ran_ue_id())); - // check that the number of NGAP UEs is increased ASSERT_EQ(ue_mng.get_nof_ngap_ues(), du_offset + it - rnti_t::MIN_CRNTI + 1); } @@ -466,11 +425,10 @@ TEST_F(ue_manager_test, when_more_than_max_ues_added_then_ngap_ue_not_created) // check that the maximum number of NGAP UEs has been reached ASSERT_EQ(ue_mng.get_nof_ngap_ues(), (du_index_to_uint(du_index_t::max) + 1) * MAX_NOF_UES_PER_DU); - ue_index_t ue_index = create_ue(du_index_t::max, MIN_PCI, rnti_t::MAX_CRNTI); + // Try allocating an additional UE index + ue_index_t ue_index = ue_mng.allocate_new_ue_index(du_index_t::max); ASSERT_EQ(ue_index, ue_index_t::invalid); - auto* ue = ue_mng.add_ue(ue_index, rrc_ue_pdu_notifier, rrc_ue_pdu_notifier, du_processor_ctrl_notifier); // check that the UE has not been added - ASSERT_EQ(ue, nullptr); ASSERT_EQ(ue_mng.get_nof_ngap_ues(), (du_index_to_uint(du_index_t::max) + 1) * MAX_NOF_UES_PER_DU); } diff --git a/tests/unittests/cu_cp/ue_manager/ue_manager_test_helpers.cpp b/tests/unittests/cu_cp/ue_manager/ue_manager_test_helpers.cpp index e8dde46694..7dd6f2136b 100644 --- a/tests/unittests/cu_cp/ue_manager/ue_manager_test_helpers.cpp +++ b/tests/unittests/cu_cp/ue_manager/ue_manager_test_helpers.cpp @@ -22,6 +22,7 @@ #include "ue_manager_test_helpers.h" #include +#include using namespace srsran; using namespace srs_cu_cp; @@ -31,6 +32,8 @@ ue_manager_test::ue_manager_test() : ue_mng(ue_config, up_config) test_logger.set_level(srslog::basic_levels::debug); ue_mng_logger.set_level(srslog::basic_levels::debug); srslog::init(); + + du_processor_ctrl_notifier = std::make_unique(ngap_ue_removal_handler); } ue_manager_test::~ue_manager_test() diff --git a/tests/unittests/cu_cp/ue_manager/ue_manager_test_helpers.h b/tests/unittests/cu_cp/ue_manager/ue_manager_test_helpers.h index b7adde4e19..1d58a02731 100644 --- a/tests/unittests/cu_cp/ue_manager/ue_manager_test_helpers.h +++ b/tests/unittests/cu_cp/ue_manager/ue_manager_test_helpers.h @@ -28,6 +28,7 @@ #include "srsran/cu_cp/cu_cp_types.h" #include +#include namespace srsran { namespace srs_cu_cp { @@ -51,7 +52,8 @@ class ue_manager_test : public ::testing::Test // DU processor to RRC UE adapters slotted_id_vector rrc_ue_adapters; dummy_ngap_rrc_ue_notifier rrc_ue_pdu_notifier; - dummy_ngap_du_processor_notifier du_processor_ctrl_notifier; + dummy_ngap_ue_context_removal_handler ngap_ue_removal_handler; + std::unique_ptr du_processor_ctrl_notifier; }; } // namespace srs_cu_cp diff --git a/tests/unittests/cu_up/cu_up_test.cpp b/tests/unittests/cu_up/cu_up_test.cpp index 3da6faa660..6f8a76dfb1 100644 --- a/tests/unittests/cu_up/cu_up_test.cpp +++ b/tests/unittests/cu_up/cu_up_test.cpp @@ -28,6 +28,7 @@ #include "srsran/support/io/io_broker_factory.h" #include "srsran/support/test_utils.h" #include +#include #include #include #include @@ -116,14 +117,15 @@ class cu_up_test : public ::testing::Test { // create config cu_up_configuration cfg; - cfg.cu_up_executor = executor.get(); - cfg.gtpu_pdu_executor = executor.get(); - cfg.e1ap.e1ap_conn_client = &e1ap_client; - cfg.f1u_gateway = f1u_gw.get(); - cfg.epoll_broker = broker.get(); - cfg.timers = app_timers.get(); - cfg.gtpu_pcap = &dummy_pcap; - cfg.net_cfg.n3_bind_port = 0; // Random free port selected by the OS. + cfg.cu_up_executor = executor.get(); + cfg.gtpu_pdu_executor = executor.get(); + cfg.e1ap.e1ap_conn_client = &e1ap_client; + cfg.f1u_gateway = f1u_gw.get(); + cfg.epoll_broker = broker.get(); + cfg.timers = app_timers.get(); + cfg.gtpu_pcap = &dummy_pcap; + cfg.net_cfg.n3_bind_port = 0; // Random free port selected by the OS. + cfg.statistics_report_period = std::chrono::seconds(1); return cfg; } diff --git a/tests/unittests/du_manager/du_manager_test_helpers.h b/tests/unittests/du_manager/du_manager_test_helpers.h index 123c08749b..9a2ae32285 100644 --- a/tests/unittests/du_manager/du_manager_test_helpers.h +++ b/tests/unittests/du_manager/du_manager_test_helpers.h @@ -56,8 +56,10 @@ class dummy_ue_executor_mapper : public du_high_ue_executor_mapper { public: explicit dummy_ue_executor_mapper(task_executor& exec_) : exec(exec_) {} - task_executor& rebind_executor(du_ue_index_t ue_index, du_cell_index_t pcell_index) override { return exec; } - task_executor& executor(du_ue_index_t ue_index) override { return exec; } + void rebind_executors(du_ue_index_t ue_index, du_cell_index_t pcell_index) override {} + task_executor& ctrl_executor(du_ue_index_t ue_index) override { return exec; } + task_executor& f1u_dl_pdu_executor(du_ue_index_t ue_index) override { return exec; } + task_executor& mac_ul_pdu_executor(du_ue_index_t ue_index) override { return exec; } task_executor& exec; }; @@ -270,6 +272,7 @@ class mac_test_dummy : public mac_cell_manager, wait_manual_event_tester wait_ue_create; wait_manual_event_tester wait_ue_reconf; wait_manual_event_tester wait_ue_delete; + bool next_ul_ccch_msg_result = true; void add_cell(const mac_cell_creation_request& cell_cfg) override {} void remove_cell(du_cell_index_t cell_index) override {} @@ -291,9 +294,10 @@ class mac_test_dummy : public mac_cell_manager, last_ue_delete_msg = msg; return wait_ue_delete.launch(); } - void handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override + bool handle_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override { last_pushed_ul_ccch_msg = std::move(pdu); + return next_ul_ccch_msg_result; } void handle_dl_buffer_state_update(const mac_dl_buffer_state_indication_message& dl_bs) override diff --git a/tests/unittests/du_manager/procedures/du_manager_procedure_test_helpers.h b/tests/unittests/du_manager/procedures/du_manager_procedure_test_helpers.h index 3812d8ef51..bffde94807 100644 --- a/tests/unittests/du_manager/procedures/du_manager_procedure_test_helpers.h +++ b/tests/unittests/du_manager/procedures/du_manager_procedure_test_helpers.h @@ -24,7 +24,7 @@ #include "../du_manager_test_helpers.h" #include "srsran/mac/config/mac_cell_group_config_factory.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" namespace srsran { namespace srs_du { @@ -66,7 +66,7 @@ class ue_manager_dummy : public du_ue_manager_repository } return nullptr; } - void handle_radio_link_failure(du_ue_index_t ue_index, rlf_cause cause) override + void handle_rlf_ue_release(du_ue_index_t ue_index, rlf_cause cause) override { last_rlf_ue_index = ue_index; last_rlf_cause = cause; @@ -78,7 +78,7 @@ class ue_manager_dummy : public du_ue_manager_repository ue_ctrl_loop.schedule(std::move(task)); } - async_task_sequencer ue_ctrl_loop{128}; + fifo_async_task_scheduler ue_ctrl_loop{128}; }; ul_ccch_indication_message create_test_ul_ccch_message(rnti_t rnti); diff --git a/tests/unittests/du_manager/procedures/ue_creation_test.cpp b/tests/unittests/du_manager/procedures/ue_creation_test.cpp index 7b6bc9dd81..15b3bd3aa4 100644 --- a/tests/unittests/du_manager/procedures/ue_creation_test.cpp +++ b/tests/unittests/du_manager/procedures/ue_creation_test.cpp @@ -179,3 +179,36 @@ TEST_F(du_manager_ue_creation_tester, ASSERT_EQ(sr_res_list1[0].offset, sr_offset1); ASSERT_EQ(sr_res_list2[0].offset, sr_offset2); } + +TEST_F(du_manager_ue_creation_tester, when_ul_ccch_flush_fails_then_ue_is_destroyed) +{ + // Start Procedure. + du_ue_index_t ue_index = to_du_ue_index(test_rgen::uniform_int(0, MAX_DU_UE_INDEX)); + rnti_t rnti = to_rnti(test_rgen::uniform_int(1, MAX_CRNTI)); + start_procedure(ue_index, rnti); + + // MAC fails to dispatch UL-CCCH to upper layers. + mac.next_ul_ccch_msg_result = false; + ASSERT_FALSE(mac.last_ue_delete_msg.has_value()); + ASSERT_FALSE(f1ap.last_ue_deleted.has_value()); + + // MAC finishes UE creation. + mac.wait_ue_create.result.allocated_crnti = rnti; + mac.wait_ue_create.result.ue_index = ue_index; + mac.wait_ue_create.result.cell_index = to_du_cell_index(0); + mac.wait_ue_create.ready_ev.set(); + + // Given that the UL-CCCH dispatch has failed, the DU should request the destruction of the UE in F1AP and MAC. + ASSERT_TRUE(f1ap.last_ue_deleted.has_value()); + ASSERT_EQ(f1ap.last_ue_deleted.value(), ue_index); + ASSERT_TRUE(mac.last_ue_delete_msg.has_value()); + ASSERT_EQ(mac.last_ue_delete_msg.value().ue_index, ue_index); + ASSERT_FALSE(proc.ready()) << "Need to wait for MAC to finish removing the UE"; + ASSERT_TRUE(ue_mng.ues.contains(ue_index)); + + mac.wait_ue_delete.result.result = true; + mac.wait_ue_delete.ready_ev.set(); + + ASSERT_FALSE(ue_mng.ues.contains(ue_index)); + ASSERT_TRUE(proc.ready()); +} \ No newline at end of file diff --git a/tests/unittests/du_manager/procedures/ue_deletion_test.cpp b/tests/unittests/du_manager/procedures/ue_deletion_test.cpp index d8c1f745a6..7bae9a4bec 100644 --- a/tests/unittests/du_manager/procedures/ue_deletion_test.cpp +++ b/tests/unittests/du_manager/procedures/ue_deletion_test.cpp @@ -117,34 +117,45 @@ TEST_F(ue_deletion_tester, when_du_manager_is_removing_ue_from_mac_then_rlc_buff TEST_F(ue_deletion_tester, when_du_manager_is_removing_ue_from_mac_then_rlf_notifications_have_no_effect) { - // MAC RLF notification should be handled. - ASSERT_TRUE(test_ue->rlf_notifier->on_rlf_detected()); + rlf_cause cause = static_cast(test_rgen::uniform_int(0, 2)); + + // RLF notification should be handled. + if (cause == rlf_cause::max_mac_kos_reached) { + test_ue->rlf_notifier->on_rlf_detected(); + } else if (cause == rlf_cause::max_rlc_retxs_reached) { + test_ue->rlf_notifier->on_max_retx(); + } else { + test_ue->rlf_notifier->on_protocol_failure(); + } + const unsigned release_timeout = + (du_cells[0].ue_timers_and_constants.t310 + du_cells[0].ue_timers_and_constants.t311).count(); + for (unsigned i = 0; i != release_timeout; ++i) { + timers.tick(); + worker.run_pending_tasks(); + } ASSERT_EQ(ue_mng.last_rlf_ue_index, test_ue->ue_index); - ASSERT_EQ(ue_mng.last_rlf_cause, rlf_cause::max_mac_kos_reached); + ASSERT_EQ(ue_mng.last_rlf_cause, cause); - // RLC RLF notification should be handled. + // RLFs have no effect after the RLF timer is triggered. + ue_mng.last_rlf_ue_index.reset(); + ue_mng.last_rlf_cause.reset(); test_ue->rlf_notifier->on_max_retx(); - ASSERT_EQ(ue_mng.last_rlf_ue_index, test_ue->ue_index); - ASSERT_EQ(ue_mng.last_rlf_cause, rlf_cause::max_rlc_retxs_reached); - - // RLC RLF notification should be handled. - test_ue->rlf_notifier->on_protocol_failure(); - ASSERT_EQ(ue_mng.last_rlf_ue_index, test_ue->ue_index); - ASSERT_EQ(ue_mng.last_rlf_cause, rlf_cause::rlc_protocol_failure); + for (unsigned i = 0; i != release_timeout; ++i) { + timers.tick(); + worker.run_pending_tasks(); + } + ASSERT_FALSE(ue_mng.last_rlf_ue_index.has_value()); + ASSERT_FALSE(ue_mng.last_rlf_cause.has_value()); // Start UE deletion. - ue_mng.last_rlf_ue_index.reset(); - ue_mng.last_rlf_cause.reset(); start_procedure(); // RLF notifications should not be handled. - ASSERT_TRUE(test_ue->rlf_notifier->on_rlf_detected()); - ASSERT_FALSE(ue_mng.last_rlf_cause.has_value()); - ASSERT_FALSE(ue_mng.last_rlf_ue_index.has_value()); - test_ue->rlf_notifier->on_max_retx(); - ASSERT_FALSE(ue_mng.last_rlf_cause.has_value()); - ASSERT_FALSE(ue_mng.last_rlf_ue_index.has_value()); - test_ue->rlf_notifier->on_protocol_failure(); + test_ue->rlf_notifier->on_rlf_detected(); + for (unsigned i = 0; i != release_timeout; ++i) { + timers.tick(); + worker.run_pending_tasks(); + } ASSERT_FALSE(ue_mng.last_rlf_cause.has_value()); ASSERT_FALSE(ue_mng.last_rlf_ue_index.has_value()); } diff --git a/tests/unittests/e1ap/common/test_helpers.h b/tests/unittests/e1ap/common/test_helpers.h index a6b77e0f8b..fb28a9f371 100644 --- a/tests/unittests/e1ap/common/test_helpers.h +++ b/tests/unittests/e1ap/common/test_helpers.h @@ -286,7 +286,9 @@ class dummy_e1ap_cu_cp_notifier : public srs_cu_cp::e1ap_cu_cp_notifier public: dummy_e1ap_cu_cp_notifier() : logger(srslog::fetch_basic_logger("TEST")){}; - void on_e1ap_created(srs_cu_cp::e1ap_bearer_context_manager& bearer_context_manager) override + void on_e1ap_created(srs_cu_cp::e1ap_bearer_context_manager& bearer_context_manager, + srs_cu_cp::e1ap_bearer_context_removal_handler& bearer_removal_handler, + srs_cu_cp::e1ap_statistics_handler& e1ap_statistic_handler) override { logger.info("Received E1AP creation notification"); } diff --git a/tests/unittests/e1ap/cu_cp/e1ap_cu_cp_ue_context_test.cpp b/tests/unittests/e1ap/cu_cp/e1ap_cu_cp_ue_context_test.cpp index b678d29353..7a601db294 100644 --- a/tests/unittests/e1ap/cu_cp/e1ap_cu_cp_ue_context_test.cpp +++ b/tests/unittests/e1ap/cu_cp/e1ap_cu_cp_ue_context_test.cpp @@ -34,7 +34,11 @@ using namespace srs_cu_cp; class e1ap_cu_cp_ue_context_test : public ::testing::Test { protected: - e1ap_cu_cp_ue_context_test() { srslog::init(); }; + e1ap_cu_cp_ue_context_test() + { + e1ap_logger.set_level(srslog::basic_levels::debug); + srslog::init(); + }; ~e1ap_cu_cp_ue_context_test() { // flush logger after each test @@ -47,10 +51,11 @@ class e1ap_cu_cp_ue_context_test : public ::testing::Test test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max) - 1)); }; - timer_manager timer_mng; - manual_task_worker ctrl_worker{128}; - timer_factory timers{timer_mng, ctrl_worker}; - e1ap_ue_context_list ue_ctxt_list{timers}; + timer_manager timer_mng; + srslog::basic_logger& e1ap_logger = srslog::fetch_basic_logger("CU-CP-E1"); + manual_task_worker ctrl_worker{128}; + timer_factory timers{timer_mng, ctrl_worker}; + e1ap_ue_context_list ue_ctxt_list{timers, e1ap_logger}; }; TEST_F(e1ap_cu_cp_ue_context_test, when_ue_added_then_ue_exists) @@ -103,15 +108,6 @@ TEST_F(e1ap_cu_cp_ue_context_test, when_ue_exists_then_removal_succeeds) ue_ctxt_list.add_ue(ue_index, cu_cp_ue_e1ap_id); - // test removal by gnb_cu_cp_ue_e1ap_id_t - ue_ctxt_list.remove_ue(cu_cp_ue_e1ap_id); - - ASSERT_FALSE(ue_ctxt_list.contains(cu_cp_ue_e1ap_id)); - ASSERT_FALSE(ue_ctxt_list.contains(ue_index)); - - ue_ctxt_list.add_ue(ue_index, cu_cp_ue_e1ap_id); - - // test removal by ue_index ue_ctxt_list.remove_ue(ue_index); ASSERT_FALSE(ue_ctxt_list.contains(cu_cp_ue_e1ap_id)); diff --git a/tests/unittests/e2/e2sm_kpm_meas_provider_test.cpp b/tests/unittests/e2/e2sm_kpm_meas_provider_test.cpp index 151bbe7dce..f984ef63aa 100644 --- a/tests/unittests/e2/e2sm_kpm_meas_provider_test.cpp +++ b/tests/unittests/e2/e2sm_kpm_meas_provider_test.cpp @@ -136,7 +136,7 @@ rlc_metrics generate_rlc_metrics(uint32_t ue_idx, uint32_t bearer_id) rlc_metric.rx.num_pdu_bytes = rlc_metric.rx.num_pdus * 1000; rlc_metric.rx.num_sdus = 5; rlc_metric.rx.num_sdu_bytes = rlc_metric.rx.num_sdus * 1000; - rlc_metric.rx.num_lost_pdus = 0; + rlc_metric.rx.num_lost_pdus = 1; rlc_metric.rx.num_malformed_pdus = 0; rlc_metric.tx.num_sdus = 10; @@ -181,7 +181,9 @@ TEST_F(e2sm_kpm_meas_provider_test, e2sm_kpm_ind_three_drb_rlc_metrics) uint32_t nof_meas_data = 5; uint32_t nof_records = 1; - uint32_t expected_drop_rate = 10; + uint32_t expected_drop_rate = 10; + uint32_t expected_ul_success_rate = 80; + uint32_t expected_ul_throughput = 5000; std::vector expected_dl_vol; std::vector expected_ul_vol; @@ -217,6 +219,10 @@ TEST_F(e2sm_kpm_meas_provider_test, e2sm_kpm_ind_three_drb_rlc_metrics) subscript_info.meas_info_list.push_back(meas_info_item); meas_info_item.meas_type.set_meas_name().from_string("DRB.RlcSduTransmittedVolumeUL"); subscript_info.meas_info_list.push_back(meas_info_item); + meas_info_item.meas_type.set_meas_name().from_string("DRB.PacketSuccessRateUlgNBUu"); + subscript_info.meas_info_list.push_back(meas_info_item); + meas_info_item.meas_type.set_meas_name().from_string("DRB.UEThpUl"); + subscript_info.meas_info_list.push_back(meas_info_item); nof_records = subscript_info.meas_info_list.size(); @@ -277,6 +283,12 @@ TEST_F(e2sm_kpm_meas_provider_test, e2sm_kpm_ind_three_drb_rlc_metrics) if (nof_records >= 3) { TESTASSERT_EQ(expected_ul_vol[ue_idx], meas_record[2].integer()); } + if (nof_records >= 4) { + TESTASSERT_EQ(expected_ul_success_rate, meas_record[3].integer()); + } + if (nof_records >= 5) { + TESTASSERT_EQ(expected_ul_throughput, meas_record[4].integer()); + } } } #if PCAP_OUTPUT @@ -284,4 +296,96 @@ TEST_F(e2sm_kpm_meas_provider_test, e2sm_kpm_ind_three_drb_rlc_metrics) packer->handle_message(e2_msg); save_msg_pcap(gw->last_pdu); #endif +} + +TEST_F(e2sm_kpm_meas_provider_test, e2sm_kpm_ind_e2_level_rlc_metrics) +{ + std::vector ue_ids = {31, 23, 152}; + std::vector nof_drbs = {3, 1, 2}; + uint32_t nof_meas_data = 5; + uint32_t nof_records = 3; + + uint32_t expected_drop_rate = 10; + uint32_t expected_dl_vol = 0; + uint32_t expected_ul_vol = 0; + + for (auto& d : nof_drbs) { + // Unit is kbit. + expected_dl_vol += (10 * 1000 * d * 8 / 1000); + expected_ul_vol += (5 * 1000 * d * 8 / 1000); + } + + // Define E2SM_KPM action format 1. + e2_sm_kpm_action_definition_s action_def; + action_def.ric_style_type = 1; + e2_sm_kpm_action_definition_format1_s& action_def_f1 = + action_def.action_definition_formats.set_action_definition_format1(); + action_def_f1.cell_global_id_present = false; + action_def_f1.granul_period = 100; + + meas_info_item_s meas_info_item; + meas_info_item.meas_type.set_meas_name().from_string("DRB.RlcPacketDropRateDl"); // Dummy metric not supported. + label_info_item_s label_info_item; + label_info_item.meas_label.no_label_present = true; + label_info_item.meas_label.no_label = meas_label_s::no_label_opts::true_value; + meas_info_item.label_info_list.push_back(label_info_item); + action_def_f1.meas_info_list.push_back(meas_info_item); + meas_info_item.meas_type.set_meas_name().from_string("DRB.RlcSduTransmittedVolumeDL"); + action_def_f1.meas_info_list.push_back(meas_info_item); + meas_info_item.meas_type.set_meas_name().from_string("DRB.RlcSduTransmittedVolumeUL"); + action_def_f1.meas_info_list.push_back(meas_info_item); + + asn1::e2ap::ri_caction_to_be_setup_item_s ric_action = generate_e2sm_kpm_ric_action(action_def); + +#if PCAP_OUTPUT + // Save E2 Subscription Request. + e2_message e2_subscript_req = generate_e2sm_kpm_subscription_request(ric_action); + packer->handle_message(e2_subscript_req); + save_msg_pcap(gw->last_pdu); +#endif + + ASSERT_TRUE(e2sm_iface->action_supported(ric_action)); + auto report_service = e2sm_iface->get_e2sm_report_service(ric_action.ric_action_definition); + + for (unsigned i = 0; i < nof_meas_data; ++i) { + // Push dummy metric measurements. + rlc_metrics rlc_metrics; + for (unsigned u = 0; u < ue_ids.size(); ++u) { + for (unsigned b = 1; b < (nof_drbs[u] + 1); ++b) { + rlc_metrics = generate_rlc_metrics(ue_ids[u], b); + du_metrics->report_metrics(rlc_metrics); + } + } + + // Trigger measurement collection. + report_service->collect_measurements(); + } + + TESTASSERT_EQ(true, report_service->is_ind_msg_ready()); + // Get RIC indication msg content. + byte_buffer ind_hdr_bytes = report_service->get_indication_header(); + byte_buffer ind_msg_bytes = report_service->get_indication_message(); + + // Decode RIC Indication and check the content. + e2_sm_kpm_ind_msg_s ric_ind_msg; + asn1::cbit_ref ric_ind_bref(ind_msg_bytes); + if (ric_ind_msg.unpack(ric_ind_bref) != asn1::SRSASN_SUCCESS) { + test_logger.debug("e2sm_kpm: RIC indication msg could not be unpacked"); + return; + } + + TESTASSERT_EQ(nof_meas_data, ric_ind_msg.ind_msg_formats.ind_msg_format1().meas_data.size()); + for (unsigned i = 0; i < nof_meas_data; ++i) { + auto& meas_record = ric_ind_msg.ind_msg_formats.ind_msg_format1().meas_data[i].meas_record; + TESTASSERT_EQ(nof_records, meas_record.size()); + TESTASSERT_EQ(expected_drop_rate, meas_record[0].integer()); + TESTASSERT_EQ(expected_dl_vol, meas_record[1].integer()); + TESTASSERT_EQ(expected_ul_vol, meas_record[2].integer()); + } + +#if PCAP_OUTPUT + e2_message e2_msg = generate_e2_ind_msg(ind_hdr_bytes, ind_msg_bytes); + packer->handle_message(e2_msg); + save_msg_pcap(gw->last_pdu); +#endif } \ No newline at end of file diff --git a/tests/unittests/f1ap/common/f1ap_asn1_helpers_test.cpp b/tests/unittests/f1ap/common/f1ap_asn1_helpers_test.cpp index 07fc260114..eaf584ec45 100644 --- a/tests/unittests/f1ap/common/f1ap_asn1_helpers_test.cpp +++ b/tests/unittests/f1ap/common/f1ap_asn1_helpers_test.cpp @@ -31,7 +31,7 @@ using namespace srsran; /// Test PLMN decoding -TEST(f1ap_asn1_helpers_test, test_ngi_converter) +TEST(f1ap_asn1_helpers_test, test_ngi_converter_for_valid_plmn) { // use known a PLMN asn1::f1ap::nr_cgi_s asn1_cgi; @@ -48,6 +48,23 @@ TEST(f1ap_asn1_helpers_test, test_ngi_converter) ASSERT_EQ("00f110", ngi.plmn_hex); // hex-encoded PLMN (like above) } +TEST(f1ap_asn1_helpers_test, test_ngi_converter_for_invalid_plmn) +{ + // use known a PLMN + asn1::f1ap::nr_cgi_s asn1_cgi; + asn1_cgi.plmn_id.from_string("00f000"); // 000.00 + asn1_cgi.nr_cell_id.from_number(6576); + + // convert to internal NGI representation + nr_cell_global_id_t ngi = cgi_from_asn1(asn1_cgi); + + ASSERT_FALSE(srsran::config_helpers::is_valid(ngi)); + ASSERT_EQ(0xf000, ngi.mcc); // BCD-encoded MCC + ASSERT_EQ(0xff00, ngi.mnc); // BCD-encoded MNC + ASSERT_EQ("00000", ngi.plmn); // human-readable PLMN + ASSERT_EQ("00f000", ngi.plmn_hex); // hex-encoded PLMN (like above) +} + static std::string create_random_ipv4_string() { std::vector nums = test_rgen::random_vector(4); diff --git a/tests/unittests/f1ap/common/f1ap_cu_test_messages.h b/tests/unittests/f1ap/common/f1ap_cu_test_messages.h index 2922646ea8..063e3265ac 100644 --- a/tests/unittests/f1ap/common/f1ap_cu_test_messages.h +++ b/tests/unittests/f1ap/common/f1ap_cu_test_messages.h @@ -22,7 +22,7 @@ #pragma once -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/f1ap/cu_cp/f1ap_cu.h" #include "srsran/ran/rnti.h" diff --git a/tests/unittests/f1ap/cu_cp/f1ap_cu_test.cpp b/tests/unittests/f1ap/cu_cp/f1ap_cu_test.cpp index 1443becebd..23a4d86515 100644 --- a/tests/unittests/f1ap/cu_cp/f1ap_cu_test.cpp +++ b/tests/unittests/f1ap/cu_cp/f1ap_cu_test.cpp @@ -180,6 +180,32 @@ TEST_F(f1ap_cu_test, when_du_to_cu_rrc_container_missing_then_ue_not_added) EXPECT_EQ(f1ap->get_nof_ues(), 0); } +TEST_F(f1ap_cu_test, when_cgi_invalid_then_ue_not_added) +{ + // Generate F1 Initial UL RRC Message + f1ap_message init_ul_rrc_msg = generate_init_ul_rrc_message_transfer(int_to_gnb_du_ue_f1ap_id(41255)); + // Set PLMN to invalid value + init_ul_rrc_msg.pdu.init_msg().value.init_ul_rrc_msg_transfer()->nr_cgi.plmn_id.from_number(0); + + // Pass message to F1AP + f1ap->handle_message(init_ul_rrc_msg); + + EXPECT_EQ(f1ap->get_nof_ues(), 0); +} + +TEST_F(f1ap_cu_test, when_rnti_invalid_then_ue_not_added) +{ + // Generate F1 Initial UL RRC Message + f1ap_message init_ul_rrc_msg = generate_init_ul_rrc_message_transfer(int_to_gnb_du_ue_f1ap_id(41255)); + // Set RNTI to invalid value + init_ul_rrc_msg.pdu.init_msg().value.init_ul_rrc_msg_transfer()->c_rnti = 0; + + // Pass message to F1AP + f1ap->handle_message(init_ul_rrc_msg); + + EXPECT_EQ(f1ap->get_nof_ues(), 0); +} + TEST_F(f1ap_cu_test, when_max_nof_ues_PER_DU_exceeded_then_ue_not_added) { // Reduce F1AP and TEST logger loglevel to warning to reduce console output diff --git a/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.cpp b/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.cpp index 8772c40bcd..f62e4e75e4 100644 --- a/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.cpp +++ b/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.cpp @@ -42,7 +42,8 @@ f1ap_cu_test::f1ap_cu_test() f1ap_logger.set_level(srslog::basic_levels::debug); srslog::init(); - f1ap = create_f1ap(f1ap_pdu_notifier, du_processor_notifier, f1ap_du_mgmt_notifier, timers, ctrl_worker); + f1ap = create_f1ap( + f1ap_pdu_notifier, du_processor_notifier, f1ap_du_mgmt_notifier, f1ap_cu_cp_notifier, timers, ctrl_worker); } f1ap_cu_test::~f1ap_cu_test() diff --git a/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.h b/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.h index cc98f4d291..a256587dc0 100644 --- a/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.h +++ b/tests/unittests/f1ap/cu_cp/f1ap_cu_test_helpers.h @@ -30,8 +30,10 @@ #include "srsran/f1ap/common/f1ap_common.h" #include "srsran/f1ap/cu_cp/f1ap_cu.h" #include "srsran/f1ap/cu_cp/f1ap_cu_factory.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/executors/manual_task_worker.h" #include +#include namespace srsran { namespace srs_cu_cp { @@ -91,6 +93,21 @@ class dummy_cu_cp_f1c_gateway std::vector> du_tx_notifiers; }; +/// Adapter between F1AP and CU-CP +class dummy_f1ap_ue_removal_notifier : public f1ap_ue_removal_notifier +{ +public: + void on_ue_removal_required(ue_index_t ue_index) override + { + logger.info("ue={}: Requested UE removal", ue_index); + last_removed_ue = ue_index; + } + +private: + ue_index_t last_removed_ue = ue_index_t::invalid; + srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST"); +}; + class dummy_f1ap_rrc_message_notifier : public srs_cu_cp::f1ap_rrc_message_notifier { public: @@ -230,6 +247,21 @@ class dummy_f1ap_du_management_notifier : public f1ap_du_management_notifier du_repository* handler = nullptr; }; +class dummy_f1ap_task_scheduler : public f1ap_task_scheduler +{ +public: + void schedule_async_task(ue_index_t ue_index, async_task&& task) override + { + if (task_loop.count(ue_index) == 0) { + task_loop.insert(std::make_pair(ue_index, std::make_unique(128))); + } + task_loop.at(ue_index)->schedule(std::move(task)); + } + +private: + std::unordered_map> task_loop; +}; + /// \brief Creates a dummy UE CONTEXT SETUP REQUEST. f1ap_ue_context_setup_request create_ue_context_setup_request(const std::initializer_list& drbs_to_add); @@ -260,7 +292,9 @@ class f1ap_cu_test : public ::testing::Test dummy_f1ap_pdu_notifier f1ap_pdu_notifier; dummy_f1ap_du_processor_notifier du_processor_notifier; dummy_f1ap_du_management_notifier f1ap_du_mgmt_notifier; + dummy_f1ap_ue_removal_notifier f1ap_cu_cp_notifier; timer_manager timers; + dummy_f1ap_task_scheduler task_sched; manual_task_worker ctrl_worker{128}; std::unique_ptr f1ap; }; diff --git a/tests/unittests/f1ap/cu_cp/f1ap_cu_ue_context_release_procedure_test.cpp b/tests/unittests/f1ap/cu_cp/f1ap_cu_ue_context_release_procedure_test.cpp index 86242a1484..c6c463a7d8 100644 --- a/tests/unittests/f1ap/cu_cp/f1ap_cu_ue_context_release_procedure_test.cpp +++ b/tests/unittests/f1ap/cu_cp/f1ap_cu_ue_context_release_procedure_test.cpp @@ -28,7 +28,7 @@ using namespace srsran; using namespace srs_cu_cp; /// Test the f1 UE context release procedure (gNB-CU initiated) -TEST_F(f1ap_cu_test, when_ue_release_command_received_then_ue_removed) +TEST_F(f1ap_cu_test, when_ue_release_command_received_then_procedure_succeeds) { // Action 1: Add UE test_logger.info("Injecting Initial UL RRC message"); @@ -58,6 +58,4 @@ TEST_F(f1ap_cu_test, when_ue_release_command_received_then_ue_removed) ASSERT_TRUE(t.ready()); ASSERT_EQ(t.get(), ue_index_t::min); - - ASSERT_EQ(f1ap->get_nof_ues(), 0); } diff --git a/tests/unittests/f1ap/cu_cp/f1ap_cu_ue_context_test.cpp b/tests/unittests/f1ap/cu_cp/f1ap_cu_ue_context_test.cpp index d3e8740179..66885a8d90 100644 --- a/tests/unittests/f1ap/cu_cp/f1ap_cu_ue_context_test.cpp +++ b/tests/unittests/f1ap/cu_cp/f1ap_cu_ue_context_test.cpp @@ -22,7 +22,7 @@ #include "f1ap_cu_test_helpers.h" #include "lib/f1ap/cu_cp/ue_context/f1ap_cu_ue_context.h" -#include "srsran/f1ap/common/f1ap_types.h" +#include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/support/executors/manual_task_worker.h" #include "srsran/support/test_utils.h" @@ -132,7 +132,7 @@ TEST_F(f1ap_cu_ue_context_test, when_ue_exists_then_removal_succeeds) ue_ctxt_list.add_ue(ue_index, cu_ue_f1ap_id); // test removal - ue_ctxt_list.remove_ue(cu_ue_f1ap_id); + ue_ctxt_list.remove_ue(ue_index); ASSERT_FALSE(ue_ctxt_list.contains(cu_ue_f1ap_id)); ASSERT_FALSE(ue_ctxt_list.contains(ue_index)); @@ -148,7 +148,7 @@ TEST_F(f1ap_cu_ue_context_test, when_ue_is_added_then_next_ue_id_is_increased) ue_ctxt_list.add_ue(ue_index, cu_ue_f1ap_id); // remove ue - ue_ctxt_list.remove_ue(cu_ue_f1ap_id); + ue_ctxt_list.remove_ue(ue_index); ASSERT_FALSE(ue_ctxt_list.contains(cu_ue_f1ap_id)); ASSERT_FALSE(ue_ctxt_list.contains(ue_index)); @@ -189,7 +189,7 @@ TEST_F(f1ap_cu_ue_context_test, when_next_ue_id_reaches_max_then_unused_values_a // remove an ue from the context list gnb_cu_ue_f1ap_id_t rem_ue_id = int_to_gnb_cu_ue_f1ap_id(19); ASSERT_TRUE(ue_ctxt_list.contains(rem_ue_id)); - ue_ctxt_list.remove_ue(rem_ue_id); + ue_ctxt_list.remove_ue(ue_ctxt_list[rem_ue_id].ue_index); ASSERT_FALSE(ue_ctxt_list.contains(rem_ue_id)); // Next available cu ue f1ap id should be the removed one diff --git a/tests/unittests/f1ap/du/f1ap_du_test_helpers.h b/tests/unittests/f1ap/du/f1ap_du_test_helpers.h index 032636ad2e..305e00d70c 100644 --- a/tests/unittests/f1ap/du/f1ap_du_test_helpers.h +++ b/tests/unittests/f1ap/du/f1ap_du_test_helpers.h @@ -32,8 +32,8 @@ #include "srsran/f1ap/du/f1ap_du.h" #include "srsran/f1ap/du/f1ap_du_factory.h" #include "srsran/f1ap/du/f1c_connection_client.h" -#include "srsran/support/async/async_task_loop.h" #include "srsran/support/async/async_test_utils.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/executors/manual_task_worker.h" #include @@ -57,10 +57,10 @@ class dummy_f1ap_du_configurator : public f1ap_du_configurator void schedule_async_task(async_task&& task) override { parent->task_loop.schedule(std::move(task)); } }; - timer_factory timers; - async_task_sequencer task_loop; - dummy_ue_task_sched ue_sched; - f1ap_du* f1ap; + timer_factory timers; + fifo_async_task_scheduler task_loop; + dummy_ue_task_sched ue_sched; + f1ap_du* f1ap; // DU manager -> F1AP. f1ap_ue_creation_request next_ue_creation_req; @@ -127,11 +127,10 @@ class dummy_ue_executor_mapper : public du_high_ue_executor_mapper public: dummy_ue_executor_mapper(task_executor& exec_) : exec(exec_) {} - task_executor& rebind_executor(du_ue_index_t ue_index, du_cell_index_t pcell_index) override - { - return executor(ue_index); - } - task_executor& executor(du_ue_index_t ue_index) override { return exec; } + void rebind_executors(du_ue_index_t ue_index, du_cell_index_t pcell_index) override {} + task_executor& ctrl_executor(du_ue_index_t ue_index) override { return exec; } + task_executor& f1u_dl_pdu_executor(du_ue_index_t ue_index) override { return exec; } + task_executor& mac_ul_pdu_executor(du_ue_index_t ue_index) override { return exec; } task_executor& exec; }; diff --git a/tests/unittests/fapi_adaptor/CMakeLists.txt b/tests/unittests/fapi_adaptor/CMakeLists.txt index e6a57d0884..141f32d0d4 100644 --- a/tests/unittests/fapi_adaptor/CMakeLists.txt +++ b/tests/unittests/fapi_adaptor/CMakeLists.txt @@ -28,5 +28,9 @@ target_link_libraries(fapi_adaptor_performance_dl_tti_request srsran_fapi_phy_me add_test(fapi_adaptor_performance_dl_tti_request fapi_adaptor_performance_dl_tti_request) add_executable(precoding_matrix_table_generator_test precoding_matrix_table_generator_test.cpp) -target_link_libraries(precoding_matrix_table_generator_test srsran_fapi_phy_message_adaptors srslog gtest gtest_main) +target_link_libraries(precoding_matrix_table_generator_test srsran_fapi_precoding_matrix_tools srslog gtest gtest_main) gtest_discover_tests(precoding_matrix_table_generator_test) + +add_executable(uci_part2_correspondence_test uci_part2_correspondence_test.cpp) +target_link_libraries(uci_part2_correspondence_test srsran_fapi_uci_part2_tools srslog gtest gtest_main) +gtest_discover_tests(uci_part2_correspondence_test) diff --git a/tests/unittests/fapi_adaptor/phy/messages/ul_pucch_pdu_test.cpp b/tests/unittests/fapi_adaptor/phy/messages/ul_pucch_pdu_test.cpp index 42da9cea2c..d67c99f753 100644 --- a/tests/unittests/fapi_adaptor/phy/messages/ul_pucch_pdu_test.cpp +++ b/tests/unittests/fapi_adaptor/phy/messages/ul_pucch_pdu_test.cpp @@ -50,11 +50,12 @@ TEST(FAPIPPHYULPUCCHAdaptorTest, ValidFormat1PDUPass) { fapi::ul_pucch_pdu fapi_pdu = build_valid_ul_pucch_f1_pdu(); - unsigned sfn = 1U; - unsigned slot = 2U; + unsigned sfn = 1U; + unsigned slot = 2U; + unsigned nof_rx_antennas = 1U; uplink_processor::pucch_pdu pdu; - convert_pucch_fapi_to_phy(pdu, fapi_pdu, sfn, slot); + convert_pucch_fapi_to_phy(pdu, fapi_pdu, sfn, slot, nof_rx_antennas); // Format 1 custom parameters. const pucch_processor::format1_configuration& phy_pdu = pdu.format1; @@ -76,6 +77,12 @@ TEST(FAPIPPHYULPUCCHAdaptorTest, ValidFormat1PDUPass) ASSERT_EQ(fapi_pdu.bit_len_harq, phy_pdu.nof_harq_ack); ASSERT_EQ(fapi_pdu.nid_pucch_hopping, phy_pdu.n_id); + // Ports. + ASSERT_EQ(nof_rx_antennas, phy_pdu.ports.size()); + for (unsigned i = 0; i != nof_rx_antennas; ++i) { + ASSERT_EQ(i, phy_pdu.ports[i]); + } + // Context parameters. check_context_parameters(pdu.context, fapi_pdu, sfn, slot, pucch_format::FORMAT_1); diff --git a/tests/unittests/fapi_adaptor/uci_part2_correspondence_test.cpp b/tests/unittests/fapi_adaptor/uci_part2_correspondence_test.cpp new file mode 100644 index 0000000000..6ab0ff79d7 --- /dev/null +++ b/tests/unittests/fapi_adaptor/uci_part2_correspondence_test.cpp @@ -0,0 +1,92 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srsran/fapi_adaptor/uci_part2_correspondence_generator.h" +#include "srsran/ran/csi_report/csi_report_on_pusch_helpers.h" +#include "srsran/ran/csi_report/csi_report_on_puxch_utils.h" +#include + +using namespace srsran; +using namespace fapi_adaptor; + +static constexpr unsigned NUM_CSI_RESOURCES = 1; + +namespace { + +class uci_part2_correspondence_generator_test + : public ::testing::TestWithParam> +{ +protected: + unsigned nof_csi_rs_resources = NUM_CSI_RESOURCES; + pmi_codebook_type codebook = std::get<0>(GetParam()); + csi_report_quantities quantities = std::get<1>(GetParam()); +}; + +} // namespace + +TEST_P(uci_part2_correspondence_generator_test, correct_generation_test) +{ + std::unique_ptr mapper; + std::unique_ptr repository; + std::tie(mapper, repository) = generate_uci_part2_correspondence(NUM_CSI_RESOURCES); + + unsigned nof_csi_rs_ports = csi_report_get_nof_csi_rs_antenna_ports(codebook); + ri_restriction_type ri_restiction(nof_csi_rs_ports); + ri_restiction.fill(0, nof_csi_rs_ports); + + csi_report_configuration report_cfg; + report_cfg.nof_csi_rs_resources = nof_csi_rs_resources; + report_cfg.pmi_codebook = codebook; + report_cfg.ri_restriction = ri_restiction; + report_cfg.quantities = quantities; + + uci_part2_size_description part2_correspondence = get_csi_report_pusch_size(report_cfg).part2_correspondence; + + // Check that the number of part2 entries match. + span info = mapper->map(report_cfg); + ASSERT_EQ(info.size(), part2_correspondence.entries.size()); + + for (unsigned i = 0, e = info.size(); i != e; ++i) { + // Check that we have the same number of part1 parameters. + ASSERT_EQ(info[i].part1_params.size(), part2_correspondence.entries[i].parameters.size()); + // Check the fields of each part1 parameter. + for (unsigned j = 0, je = info[i].part1_params.size(); j != je; ++j) { + ASSERT_EQ(info[i].part1_params[j].offset, part2_correspondence.entries[i].parameters[j].offset); + ASSERT_EQ(info[i].part1_params[j].bitwidth, part2_correspondence.entries[i].parameters[j].width); + } + + span indices = repository->get_uci_part2_correspondence(info[i].part2_map_index); + // Check the map field matches to the contents of the repository. + for (unsigned j = 0, je = indices.size(); j != je; ++j) { + ASSERT_EQ(indices[j], part2_correspondence.entries[i].map[j]); + } + } +} + +INSTANTIATE_TEST_SUITE_P(uci_part2, + uci_part2_correspondence_generator_test, + testing::Combine(testing::Values(pmi_codebook_type::one, + pmi_codebook_type::two, + pmi_codebook_type::typeI_single_panel_4ports_mode1), + testing::Values(csi_report_quantities::cri_ri_cqi, + csi_report_quantities::cri_ri_pmi_cqi, + csi_report_quantities::cri_ri_li_pmi_cqi))); diff --git a/tests/unittests/mac/CMakeLists.txt b/tests/unittests/mac/CMakeLists.txt index a50d069a78..84e84e10f2 100644 --- a/tests/unittests/mac/CMakeLists.txt +++ b/tests/unittests/mac/CMakeLists.txt @@ -43,7 +43,7 @@ add_test(mac_ul_processor_test mac_ul_processor_test) add_executable(mac_dl_test mac_dl_cfg_test.cpp - mac_dl_ue_manager_test.cpp + mac_rlf_detector_test.cpp mac_cell_processor_test.cpp dl_sch_pdu_assembler_test.cpp mac_rar_pdu_assembler_test.cpp) diff --git a/tests/unittests/mac/dl_sch_pdu_assembler_test.cpp b/tests/unittests/mac/dl_sch_pdu_assembler_test.cpp index 8a652e25c5..25ed959634 100644 --- a/tests/unittests/mac/dl_sch_pdu_assembler_test.cpp +++ b/tests/unittests/mac/dl_sch_pdu_assembler_test.cpp @@ -152,7 +152,7 @@ class dummy_dl_bearer : public mac_sdu_tx_builder class mac_dl_sch_assembler_tester : public testing::Test { public: - mac_dl_sch_assembler_tester() : ue_mng(rnti_table, rlf_handler), dl_bearers(2), dl_sch_enc(ue_mng) + mac_dl_sch_assembler_tester() : ue_mng(rnti_table), dl_bearers(2), dl_sch_enc(ue_mng) { srslog::fetch_basic_logger("MAC", true).set_level(srslog::basic_levels::debug); srslog::init(); @@ -168,14 +168,11 @@ class mac_dl_sch_assembler_tester : public testing::Test req.bearers[i].dl_bearer = &dl_bearers[i]; } req.ul_ccch_msg = &msg3_pdu; - std::vector> harq_buffers; - harq_buffers.resize(MAX_NOF_HARQS); - for (auto& h : harq_buffers) { - h.resize(MAX_DL_PDU_LENGTH); - } rnti_table.add_ue(req.crnti, req.ue_index); - ue_mng.add_ue(req, std::move(harq_buffers)); + + mac_dl_ue_context u{req}; + ue_mng.add_ue(std::move(u)); } ~mac_dl_sch_assembler_tester() { srslog::flush(); } @@ -184,7 +181,6 @@ class mac_dl_sch_assembler_tester : public testing::Test byte_buffer msg3_pdu; mac_ue_create_request req = test_helpers::make_default_ue_creation_request(); du_rnti_table rnti_table; - rlf_detector rlf_handler{10000, 10000}; mac_dl_ue_manager ue_mng; std::vector dl_bearers; dl_sch_pdu_assembler dl_sch_enc; diff --git a/tests/unittests/mac/mac_cell_processor_test.cpp b/tests/unittests/mac/mac_cell_processor_test.cpp index c3432dc85f..520d60fa58 100644 --- a/tests/unittests/mac/mac_cell_processor_test.cpp +++ b/tests/unittests/mac/mac_cell_processor_test.cpp @@ -37,7 +37,7 @@ class mac_cell_processor_tester : public ::testing::TestWithParam { protected: mac_cell_processor_tester() : - ue_mng(rnti_table, rlf_handler), + ue_mng(rnti_table), mac_cell(test_helpers::make_default_mac_cell_config(), sched_adapter, ue_mng, @@ -51,7 +51,6 @@ class mac_cell_processor_tester : public ::testing::TestWithParam test_helpers::dummy_mac_scheduler_adapter sched_adapter; du_rnti_table rnti_table; - rlf_detector rlf_handler{10000, 10000}; mac_dl_ue_manager ue_mng; test_helpers::dummy_mac_cell_result_notifier phy_notifier; manual_task_worker task_worker{128}; diff --git a/tests/unittests/mac/mac_ctrl_test_dummies.h b/tests/unittests/mac/mac_ctrl_test_dummies.h index e98e63e455..44625c1248 100644 --- a/tests/unittests/mac/mac_ctrl_test_dummies.h +++ b/tests/unittests/mac/mac_ctrl_test_dummies.h @@ -58,7 +58,11 @@ class mac_ul_dummy_configurer final : public mac_ul_configurator const std::vector& ul_logical_channels) override; async_task remove_bearers(du_ue_index_t ue_index, span lcids_to_rem) override; async_task remove_ue(const mac_ue_delete_request& msg) override; - void flush_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override { ul_ccch_forwarded = true; } + bool flush_ul_ccch_msg(du_ue_index_t ue_index, byte_buffer pdu) override + { + ul_ccch_forwarded = true; + return true; + } }; class mac_cell_dummy_controller final : public mac_cell_controller @@ -97,11 +101,10 @@ class dummy_ue_executor_mapper : public du_high_ue_executor_mapper public: dummy_ue_executor_mapper(task_executor& exec_) : exec(exec_) {} - task_executor& rebind_executor(du_ue_index_t ue_index, du_cell_index_t pcell_index) override - { - return executor(ue_index); - } - task_executor& executor(du_ue_index_t ue_index) override { return exec; } + void rebind_executors(du_ue_index_t ue_index, du_cell_index_t pcell_index) override {} + task_executor& ctrl_executor(du_ue_index_t ue_index) override { return exec; } + task_executor& f1u_dl_pdu_executor(du_ue_index_t ue_index) override { return exec; } + task_executor& mac_ul_pdu_executor(du_ue_index_t ue_index) override { return exec; } task_executor& exec; }; @@ -134,6 +137,8 @@ class dummy_mac_event_indicator : public mac_ul_ccch_notifier last_ccch_ind.value().slot_rx == test_msg.slot_rx && last_ccch_ind.value().subpdu == test_msg.subpdu; return test; } + + bool verify_no_ul_ccch_msg() const { return not last_ccch_ind.has_value(); } }; class dummy_mac_cell_result_notifier : public mac_cell_result_notifier diff --git a/tests/unittests/mac/mac_dl_cfg_test.cpp b/tests/unittests/mac/mac_dl_cfg_test.cpp index b4db30a62b..c0284594be 100644 --- a/tests/unittests/mac/mac_dl_cfg_test.cpp +++ b/tests/unittests/mac/mac_dl_cfg_test.cpp @@ -145,10 +145,9 @@ void test_dl_ue_procedure_execution_contexts() dummy_mac_event_indicator du_mng_notifier; dummy_mac_result_notifier phy_notifier; dummy_scheduler_ue_metrics_notifier metrics_notif; - rlf_detector rlf_handler{10000, 10000}; test_helpers::dummy_mac_pcap pcap; - mac_dl_config mac_dl_cfg{ul_exec_mapper, dl_exec_mapper, ctrl_worker, phy_notifier, pcap, rlf_handler}; - mac_config maccfg{du_mng_notifier, + mac_dl_config mac_dl_cfg{ul_exec_mapper, dl_exec_mapper, ctrl_worker, phy_notifier, pcap}; + mac_config maccfg{du_mng_notifier, ul_exec_mapper, dl_exec_mapper, ctrl_worker, @@ -157,9 +156,9 @@ void test_dl_ue_procedure_execution_contexts() pcap, scheduler_expert_config{}, metrics_notif}; - rnti_manager rnti_mng; + rnti_manager rnti_mng; - srsran_scheduler_adapter sched_cfg_adapter{maccfg, rnti_mng, rlf_handler}; + srsran_scheduler_adapter sched_cfg_adapter{maccfg, rnti_mng}; mac_dl_processor mac_dl(mac_dl_cfg, sched_cfg_adapter, rnti_mng); // Action: Add Cell. @@ -200,11 +199,10 @@ void test_dl_ue_procedure_tsan() dummy_dl_executor_mapper dl_exec_mapper{&dl_execs[0], &dl_execs[1]}; dummy_mac_event_indicator du_mng_notifier; dummy_mac_result_notifier phy_notifier; - rlf_detector rlf_handler{10000, 10000}; test_helpers::dummy_mac_pcap pcap; dummy_scheduler_ue_metrics_notifier metrics_notif; - mac_dl_config mac_dl_cfg{ul_exec_mapper, dl_exec_mapper, ctrl_worker, phy_notifier, pcap, rlf_handler}; - mac_config maccfg{du_mng_notifier, + mac_dl_config mac_dl_cfg{ul_exec_mapper, dl_exec_mapper, ctrl_worker, phy_notifier, pcap}; + mac_config maccfg{du_mng_notifier, ul_exec_mapper, dl_exec_mapper, ctrl_worker, @@ -213,9 +211,9 @@ void test_dl_ue_procedure_tsan() pcap, scheduler_expert_config{}, metrics_notif}; - rnti_manager rnti_mng; + rnti_manager rnti_mng; - srsran_scheduler_adapter sched_cfg_adapter{maccfg, rnti_mng, rlf_handler}; + srsran_scheduler_adapter sched_cfg_adapter{maccfg, rnti_mng}; mac_dl_processor mac_dl(mac_dl_cfg, sched_cfg_adapter, rnti_mng); // Action: Add Cells. diff --git a/tests/unittests/mac/mac_dl_ue_manager_test.cpp b/tests/unittests/mac/mac_dl_ue_manager_test.cpp deleted file mode 100644 index e4543d65d5..0000000000 --- a/tests/unittests/mac/mac_dl_ue_manager_test.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * - * Copyright 2021-2023 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "lib/mac/mac_dl/mac_dl_ue_manager.h" -#include "mac_test_helpers.h" -#include "srsran/support/test_utils.h" -#include - -using namespace srsran; - -class mac_dl_ue_manager_tester : public ::testing::Test -{ -protected: - static constexpr unsigned MAX_KOS = 100; - - mac_dl_ue_manager_tester() : ue_mng(rnti_table, rlf_handler) - { - srslog::init(); - srslog::fetch_basic_logger("MAC").set_level(srslog::basic_levels::debug); - } - ~mac_dl_ue_manager_tester() { srslog::flush(); } - - du_rnti_table rnti_table; - rlf_detector rlf_handler{MAX_KOS, MAX_KOS}; - mac_dl_ue_manager ue_mng; - - test_helpers::dummy_ue_rlf_notifier rlf_notifier; - - void add_ue(rnti_t rnti, du_ue_index_t ue_index) - { - this->rnti_table.add_ue(rnti, ue_index); - - mac_ue_create_request req = test_helpers::make_default_ue_creation_request(); - req.ue_index = ue_index; - req.crnti = rnti; - req.rlf_notifier = &rlf_notifier; - this->ue_mng.add_ue(req, {}); - } -}; - -TEST_F(mac_dl_ue_manager_tester, when_consecutive_harq_kos_reaches_limit_then_rlf_is_triggered) -{ - du_ue_index_t ue_index = to_du_ue_index(0); - add_ue(to_rnti(0x4601), ue_index); - - unsigned nof_oks = test_rgen::uniform_int(0, 10); - for (unsigned i = 0; i != nof_oks; ++i) { - this->ue_mng.report_ack(ue_index, true); - } - - for (unsigned i = 0; i != MAX_KOS; ++i) { - ASSERT_FALSE(this->rlf_notifier.rlf_detected); - this->ue_mng.report_ack(ue_index, false); - } - ASSERT_TRUE(this->rlf_notifier.rlf_detected); -} - -TEST_F(mac_dl_ue_manager_tester, when_harq_kos_limit_not_reached_then_rlf_is_not_triggered) -{ - du_ue_index_t ue_index = to_du_ue_index(0); - add_ue(to_rnti(0x4601), ue_index); - - for (unsigned i = 0; i != MAX_KOS; ++i) { - this->ue_mng.report_ack(ue_index, i == 0); - } - ASSERT_FALSE(this->rlf_notifier.rlf_detected); -} diff --git a/tests/unittests/mac/mac_rlf_detector_test.cpp b/tests/unittests/mac/mac_rlf_detector_test.cpp new file mode 100644 index 0000000000..f51aed49ed --- /dev/null +++ b/tests/unittests/mac/mac_rlf_detector_test.cpp @@ -0,0 +1,81 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "lib/mac/mac_sched/rlf_detector.h" +#include "mac_test_helpers.h" +#include "srsran/support/test_utils.h" +#include + +using namespace srsran; + +class mac_rlf_detector_test : public ::testing::Test +{ +protected: + static constexpr unsigned MAX_KOS = 100; + + mac_rlf_detector_test() : rlf_handler(MAX_KOS, MAX_KOS) + { + srslog::init(); + srslog::fetch_basic_logger("MAC").set_level(srslog::basic_levels::debug); + } + ~mac_rlf_detector_test() { srslog::flush(); } + + test_helpers::dummy_ue_rlf_notifier rlf_notif; + rlf_detector rlf_handler; +}; + +TEST_F(mac_rlf_detector_test, when_consecutive_harq_kos_reaches_limit_then_rlf_is_triggered) +{ + du_ue_index_t ue_index = to_du_ue_index(0); + rlf_handler.add_ue(ue_index, rlf_notif); + + unsigned nof_oks = test_rgen::uniform_int(0, MAX_KOS + 1); + for (unsigned i = 0; i != nof_oks; ++i) { + rlf_handler.handle_ack(ue_index, true); + } + + for (unsigned i = 0; i != MAX_KOS; ++i) { + ASSERT_FALSE(rlf_notif.rlf_detected); + rlf_handler.handle_ack(ue_index, false); + } + ASSERT_TRUE(rlf_notif.rlf_detected); + + // Check that the RLF is not triggered again, if more KOs are detected. + rlf_notif.rlf_detected = false; + for (unsigned i = 0; i != MAX_KOS; ++i) { + rlf_handler.handle_ack(ue_index, false); + } + ASSERT_FALSE(rlf_notif.rlf_detected); +} + +TEST_F(mac_rlf_detector_test, when_consecutive_harq_kos_limit_not_reached_then_rlf_is_not_triggered) +{ + du_ue_index_t ue_index = to_du_ue_index(0); + rlf_handler.add_ue(ue_index, rlf_notif); + + for (unsigned i = 0; i != MAX_KOS - 1; ++i) { + rlf_handler.handle_ack(ue_index, false); + } + rlf_handler.handle_ack(ue_index, true); + rlf_handler.handle_ack(ue_index, false); + ASSERT_FALSE(rlf_notif.rlf_detected); +} diff --git a/tests/unittests/mac/mac_test_helpers.h b/tests/unittests/mac/mac_test_helpers.h index d3aa8129a0..991572fcd0 100644 --- a/tests/unittests/mac/mac_test_helpers.h +++ b/tests/unittests/mac/mac_test_helpers.h @@ -56,20 +56,19 @@ inline mac_cell_creation_request make_default_mac_cell_config(const cell_config_ for (unsigned i = 0; i != 100; ++i) { dummy_sib1.append(i); } - req.bcch_dl_sch_payload = std::move(dummy_sib1); + req.bcch_dl_sch_payloads.push_back(std::move(dummy_sib1)); return req; } class dummy_ue_rlf_notifier : public mac_ue_radio_link_notifier { public: - bool rlf_detected = false; + bool rlf_detected = false; + bool crnti_ce_detected = false; - bool on_rlf_detected() override - { - rlf_detected = true; - return true; - } + void on_rlf_detected() override { rlf_detected = true; } + + void on_crnti_ce_received() override { crnti_ce_detected = true; } }; inline mac_ue_create_request make_default_ue_creation_request() @@ -184,7 +183,7 @@ class dummy_mac_pcap : public mac_pcap public: ~dummy_mac_pcap() override = default; - void open(const std::string& filename_) override {} + void open(const std::string& filename_, mac_pcap_type type) override {} void close() override {} bool is_write_enabled() override { return false; } void push_pdu(mac_nr_context_info context, const_span pdu) override {} diff --git a/tests/unittests/mac/mac_ul_pdu_test.cpp b/tests/unittests/mac/mac_ul_pdu_test.cpp index fe97cd8aae..d62be28f9e 100644 --- a/tests/unittests/mac/mac_ul_pdu_test.cpp +++ b/tests/unittests/mac/mac_ul_pdu_test.cpp @@ -487,7 +487,7 @@ TEST(mac_ul_pdu, decode_short_sdu) enc.pack(0, 1); // F. enc.pack(lcid, 6); // LCID. enc.pack(L, 8); // L. - msg.append(payload); + ASSERT_TRUE(msg.append(payload)); mac_ul_sch_pdu pdu; ASSERT_TRUE(pdu.unpack(msg)); @@ -524,7 +524,7 @@ TEST(mac_ul_pdu, decode_long_sdu) enc.pack(1, 1); // F. enc.pack(lcid, 6); // LCID. enc.pack(L, 16); // L (2 octets). - msg.append(payload); + ASSERT_TRUE(msg.append(payload)); mac_ul_sch_pdu pdu; ASSERT_TRUE(pdu.unpack(msg)); @@ -562,7 +562,7 @@ TEST(mac_ul_pdu, handle_the_case_when_a_pdu_has_too_many_subpdus) enc.pack(0, 1); // F. enc.pack(lcid, 6); // LCID. enc.pack(L, 8); // L. - msg.append(payload); + ASSERT_TRUE(msg.append(payload)); } mac_ul_sch_pdu pdu; diff --git a/tests/unittests/mac/mac_ul_processor_test.cpp b/tests/unittests/mac/mac_ul_processor_test.cpp index 0c2118343d..511757c66d 100644 --- a/tests/unittests/mac/mac_ul_processor_test.cpp +++ b/tests/unittests/mac/mac_ul_processor_test.cpp @@ -22,6 +22,7 @@ #include "lib/mac/mac_ul/mac_scheduler_ce_info_handler.h" #include "lib/mac/mac_ul/mac_ul_processor.h" +#include "lib/mac/rnti_manager.h" #include "mac_ctrl_test_dummies.h" #include "mac_test_helpers.h" #include "srsran/scheduler/scheduler_feedback_handler.h" @@ -40,6 +41,7 @@ class dummy_sched_ce_info_handler : public mac_scheduler_ce_info_handler optional last_bsr_msg; optional last_sched_cmd; optional last_ce_cmd; + optional last_crnti_ce; /// \brief Forward to scheduler any decoded UL BSRs for a given UE. void handle_ul_bsr_indication(const mac_bsr_ce_info& bsr) override { last_bsr_msg = bsr; } @@ -76,7 +78,9 @@ class dummy_sched_ce_info_handler : public mac_scheduler_ce_info_handler } /// \brief Forward to scheduler any decoded UL PHRs for a given UE. - virtual void handle_ul_phr_indication(const mac_phr_ce_info& phr) override { last_phr_msg = phr; } + void handle_ul_phr_indication(const mac_phr_ce_info& phr) override { last_phr_msg = phr; } + + void handle_crnti_ce_indication(du_ue_index_t old_ue_index) override { last_crnti_ce = old_ue_index; } /// Compare verify_phr_msg with a test message passed to the function. // TODO: Handle verification of Multiple Entry PHR. @@ -110,12 +114,15 @@ struct test_bench { add_ue(rnti, du_ue_idx); } - // Add a UE to the RNTI table. - void add_ue(rnti_t rnti, du_ue_index_t du_ue_idx, unsigned activity_timeout = DEFAULT_ACTIVITY_TIMEOUT) + // Allocate new TC-RNTI. + rnti_t allocate_tc_rnti() { return rnti_mng.allocate(); } + + // Add a UE to the RNTI table and UE context repository. + void add_ue(rnti_t rnti, du_ue_index_t du_ue_idx) { - srsran_assert(not rnti_table.has_rnti(rnti), "RNTI={:#x} already exists", rnti); + srsran_assert(not rnti_mng.has_rnti(rnti), "RNTI={:#x} already exists", rnti); srsran_assert(not test_ues.contains(du_ue_idx), "ueId={:#x} already exists", rnti); - rnti_table.add_ue(rnti, du_ue_idx); + rnti_mng.add_ue(rnti, du_ue_idx); test_ues.emplace(du_ue_idx); test_ues[du_ue_idx].rnti = rnti; test_ues[du_ue_idx].ue_index = du_ue_idx; @@ -130,7 +137,7 @@ struct test_bench { { // Create PDU content. mac_rx_pdu pdu{.rnti = rnti, .rapid = 1, .harq_id = 0}; - pdu.pdu.append(pdu_payload); + ASSERT_TRUE(pdu.pdu.append(pdu_payload)); // Add PDU to the list in the RX indication message. rx_msg_sbsr.pdus.push_back(pdu); @@ -143,7 +150,13 @@ struct test_bench { enqueue_pdu(rnti, pdu_payload); // Send RX data indication to MAC UL. - mac_ul.handle_rx_data_indication(rx_msg_sbsr); + send_rx_indication_msg(rx_msg_sbsr); + } + + void send_rx_indication_msg(mac_rx_data_indication rx_ind) + { + // Send RX data indication to MAC UL. + mac_ul.handle_rx_data_indication(rx_ind); // Call task executor manually. while (task_exec.has_pending_tasks()) { @@ -166,6 +179,9 @@ struct test_bench { return du_mng_notifier.verify_ul_ccch_msg(test_msg); } + // Call the dummy DU notifier to ensure no UL CCCH indication was forwarded. + bool verify_no_ul_ccch_msg() { return du_mng_notifier.verify_no_ul_ccch_msg(); } + const slotted_array& get_test_ues() const { return test_ues; } void run_slot() @@ -187,15 +203,17 @@ struct test_bench { return verify_no_sr_notification() or sched_ce_handler.last_sched_cmd->rnti != rnti; } + const dummy_sched_ce_info_handler& sched_ce_notifier() { return sched_ce_handler; } + private: srslog::basic_logger& logger = srslog::fetch_basic_logger("MAC", true); manual_task_worker task_exec{128}; dummy_ue_executor_mapper ul_exec_mapper{task_exec}; dummy_mac_event_indicator du_mng_notifier; - du_rnti_table rnti_table; + rnti_manager rnti_mng; dummy_sched_ce_info_handler sched_ce_handler; dummy_mac_pcap pcap; - mac_ul_config cfg{task_exec, ul_exec_mapper, du_mng_notifier, sched_ce_handler, rnti_table, pcap}; + mac_ul_config cfg{task_exec, ul_exec_mapper, du_mng_notifier, sched_ce_handler, rnti_mng, pcap}; // This is the RNTI of the UE that appears in the mac_rx_pdu created by send_rx_indication_msg() du_cell_index_t cell_idx; mac_ul_processor mac_ul{cfg}; @@ -204,30 +222,37 @@ struct test_bench { slotted_array test_ues; }; +mac_rx_data_indication create_rx_data_indication(du_cell_index_t cell_idx, rnti_t rnti, byte_buffer pdu_payload) +{ + mac_rx_data_indication rx_ind{slot_point{subcarrier_spacing::kHz15, 0}, cell_idx, {}}; + rx_ind.pdus.push_back(mac_rx_pdu{.rnti = rnti, .rapid = 1, .harq_id = 0}); + rx_ind.pdus.back().pdu = std::move(pdu_payload); + return rx_ind; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Test UL MAC processing of RX indication message with MAC PDU for UL CCCH 48 bits. TEST(mac_ul_processor, decode_ul_ccch_48bit) { // Define UE and create test_bench. - rnti_t ue1_rnti = to_rnti(0x4602); - du_ue_index_t ue1_idx = to_du_ue_index(1U); du_cell_index_t cell_idx = to_du_cell_index(0U); - test_bench t_bench(ue1_rnti, ue1_idx, cell_idx); + test_bench t_bench(cell_idx); + rnti_t tc_rnti = t_bench.allocate_tc_rnti(); // Create PDU content. // R/LCID MAC subheader | MAC SDU (UL CCCH 48 bits) // { 0x34 | 0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06} (Random 6B sequence) - byte_buffer pdu({0x34, 0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06}); + byte_buffer payload({0x34, 0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06}); // Send RX data indication to MAC UL. - t_bench.send_rx_indication_msg(to_rnti(0x4601), pdu); + t_bench.send_rx_indication_msg(tc_rnti, payload); // Create UL CCCH indication msg to verify MAC processing of PDU. struct ul_ccch_indication_message ul_ccch_msg {}; ul_ccch_msg.cell_index = cell_idx; ul_ccch_msg.slot_rx = slot_point{0, 1}; - ul_ccch_msg.tc_rnti = to_rnti(0x4601); + ul_ccch_msg.tc_rnti = tc_rnti; // Remove R/R/LCID header (0x34) from PDU ul_ccch_msg.subpdu.append({0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06}); @@ -239,24 +264,23 @@ TEST(mac_ul_processor, decode_ul_ccch_48bit) TEST(mac_ul_processor, decode_ul_ccch_64bit) { // Define UE and create test_bench. - rnti_t ue1_rnti = to_rnti(0x4602); - du_ue_index_t ue1_idx = to_du_ue_index(0U); du_cell_index_t cell_idx = to_du_cell_index(0U); - test_bench t_bench(ue1_rnti, ue1_idx, cell_idx); + test_bench t_bench(cell_idx); + rnti_t tc_rnti = t_bench.allocate_tc_rnti(); // Create PDU content. // R/LCID MAC subheader | MAC SDU (UL CCCH 64 bits) // { 0x00 | 0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06, 0x13, 0x54} (Random 8B sequence) - byte_buffer pdu({0x00, 0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06, 0x13, 0x54}); + byte_buffer payload({0x00, 0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06, 0x13, 0x54}); // Send RX data indication to MAC UL. - t_bench.send_rx_indication_msg(to_rnti(0x4601), pdu); + t_bench.send_rx_indication_msg(tc_rnti, payload); // Create UL CCCH indication msg to verify MAC processing of PDU. struct ul_ccch_indication_message ul_ccch_msg {}; ul_ccch_msg.cell_index = cell_idx; ul_ccch_msg.slot_rx = slot_point{0, 1}; - ul_ccch_msg.tc_rnti = to_rnti(0x4601); + ul_ccch_msg.tc_rnti = tc_rnti; // Remove R/R/LCID header (0x00) from PDU ul_ccch_msg.subpdu.append({0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06, 0x13, 0x54}); @@ -381,12 +405,12 @@ TEST(mac_ul_processor, decode_invalid_long_bsr) TEST(mac_ul_processor, decode_crnti_ce) { // Define UE and create test_bench. - rnti_t ue1_rnti = to_rnti(0x4602); - du_ue_index_t ue1_idx = to_du_ue_index(2U); du_cell_index_t cell_idx = to_du_cell_index(1U); - test_bench t_bench(ue1_rnti, ue1_idx, cell_idx); + test_bench t_bench(cell_idx); // Add a UE. This RNTI (0x4601) is the one carried by the MAC CE C-RNTI and should be used in the sr_ind{} below. t_bench.add_ue(to_rnti(0x4601), to_du_ue_index(1U)); + // Add a TC-RNTI, which will be used to send the C-RNTI CE in Msg3. + rnti_t tc_rnti = t_bench.allocate_tc_rnti(); // Create PDU content. // R/LCID MAC subheader | MAC CE C-RNTI @@ -394,48 +418,52 @@ TEST(mac_ul_processor, decode_crnti_ce) byte_buffer pdu({0x3a, 0x46, 0x01}); // Send RX data indication to MAC UL. - t_bench.send_rx_indication_msg(ue1_rnti, pdu); + t_bench.send_rx_indication_msg(tc_rnti, pdu); + + // Test that C-RNTI CE was notified to the scheduler. + ASSERT_EQ(t_bench.sched_ce_notifier().last_crnti_ce, to_du_ue_index(1U)); // Test if notification sent to Scheduler has been received and it is correct. ASSERT_TRUE(t_bench.verify_sched_req_notification(to_du_ue_index(1))); } // Test UL MAC processing of RX indication message with MAC PDU for multiple subPDUs (MAC CE C-RNTI, MAC CE Short BSR). +// The BSR should be directed at the old C-RNTI rather than the TC-RNTI. TEST(mac_ul_processor, decode_crnti_ce_and_sbsr) { // Define UE and create test_bench. - rnti_t ue1_rnti = to_rnti(0x4602); - du_ue_index_t ue1_idx = to_du_ue_index(2U); du_cell_index_t cell_idx = to_du_cell_index(1U); - test_bench t_bench(ue1_rnti, ue1_idx, cell_idx); - // Add a UE. This RNTI (0x4601) is the one carried by the MAC CE C-RNTI and should be used in the sr_ind{} below - t_bench.add_ue(to_rnti(0x4601), to_du_ue_index(1U)); + test_bench t_bench(to_rnti(0x4601), to_du_ue_index(0U), cell_idx); + // Allocate TC-RNTI which will be used to transfer MAC CE C-RNTI and MAC CE Short BSR. + rnti_t tc_rnti = t_bench.allocate_tc_rnti(); - // Create subPDU content. + // Create PDU content. + byte_buffer payload; + // > Create MAC CE C-RNTI subPDU. // R/LCID MAC subheader | MAC CE C-RNTI // { 0x3a | 0x46, 0x01 } - byte_buffer pdu_ce_crnti({0x3a, 0x46, 0x01}); - t_bench.enqueue_pdu(ue1_rnti, pdu_ce_crnti); - - // Create subPDU content. + ASSERT_TRUE(payload.append(byte_buffer{0x3a, 0x46, 0x01})); + // > Create MAC CE Short BSR subPDU. // R/LCID MAC subheader | MAC CE Short BSR // { 0x3d | 0x59} - byte_buffer pdu_sbsr({0x3d, 0x59}); + ASSERT_TRUE(payload.append(byte_buffer{0x3d, 0x59})); // Send RX data indication to MAC UL - t_bench.send_rx_indication_msg(ue1_rnti, pdu_sbsr); + t_bench.send_rx_indication_msg(create_rx_data_indication(cell_idx, tc_rnti, std::move(payload))); - // Create UL Sched Req indication message (generated by MAC CE C-RNTI) to compare with one passed to the scheduler. - // Test if notification sent to Scheduler has been received and it is correct. - ASSERT_TRUE(t_bench.verify_sched_req_notification(to_du_ue_index(1U))); + // Given that a BSR was included in the Msg3, there is no need to schedule a SR notification to complete the RA + // procedure. + ASSERT_TRUE(t_bench.verify_no_sr_notification()); // Create UL BSR indication message to compare with one passed to the scheduler. + // Note: The C-RNTI should correspond to the old C-RNTI. mac_bsr_ce_info bsr; bsr.cell_index = cell_idx; - bsr.ue_index = ue1_idx; - bsr.rnti = ue1_rnti; + bsr.ue_index = to_du_ue_index(0); + bsr.rnti = to_rnti(0x4601); bsr.bsr_fmt = bsr_format::SHORT_BSR; bsr.lcg_reports = {lcg_bsr_report{.lcg_id = uint_to_lcg_id(2U), .buffer_size = 25}}; ASSERT_NO_FATAL_FAILURE(t_bench.verify_sched_bsr_notification(bsr)); + ASSERT_TRUE(t_bench.verify_no_bsr_notification(tc_rnti)); } // Test UL MAC processing of RX indication message with MAC PDU for multiple subPDUs (MAC CE C-RNTI, MAC CE Short BSR), @@ -443,27 +471,25 @@ TEST(mac_ul_processor, decode_crnti_ce_and_sbsr) TEST(mac_ul_processor, handle_crnti_ce_with_inexistent_old_crnti) { // Define UE and create test_bench. - rnti_t ue2_rnti = to_rnti(0x4602); - du_ue_index_t ue2_idx = to_du_ue_index(2U); du_cell_index_t cell_idx = to_du_cell_index(0U); test_bench t_bench(cell_idx); - t_bench.add_ue(ue2_rnti, ue2_idx); + rnti_t tc_rnti = t_bench.allocate_tc_rnti(); // Create PDU content. - byte_buffer pdu; + byte_buffer payload; // > Create subPDU content. // R/LCID MAC subheader | MAC CE C-RNTI // { 0x3a | 0x46, 0x01 } byte_buffer ce_crnti({0x3a, 0x46, 0x01}); - pdu.append(ce_crnti); + ASSERT_TRUE(payload.append(ce_crnti)); // > Create subPDU content. // R/LCID MAC subheader | MAC CE Short BSR // { 0x3d | 0x59} byte_buffer sbsr({0x3d, 0x59}); - pdu.append(sbsr); + ASSERT_TRUE(payload.append(sbsr)); // Send RX data indication to MAC UL - t_bench.send_rx_indication_msg(ue2_rnti, pdu); + t_bench.send_rx_indication_msg(create_rx_data_indication(cell_idx, tc_rnti, std::move(payload))); // Ensure Scheduler did not get notified of any BSR. ASSERT_TRUE(t_bench.verify_no_bsr_notification()); @@ -510,3 +536,51 @@ TEST(mac_ul_processor, verify_single_entry_phr) // Test if notification sent to Scheduler has been received and it is correct. ASSERT_NO_FATAL_FAILURE(t_bench.verify_sched_phr_notification(phr_ind)); } + +// Test UL MAC processing of RX indication message with MAC PDU with UL-CCCH CE and Single Entry PHR CE. +TEST(mac_ul_processor, when_ul_ccch_and_phr_are_received_then_phr_is_ignored) +{ + // Define UE and create test_bench. + const du_cell_index_t cell_idx = to_du_cell_index(1U); + test_bench t_bench(cell_idx); + rnti_t tc_rnti = t_bench.allocate_tc_rnti(); + + // Create PDU content. + byte_buffer payload; + // > MAC subPDU with UL-CCCH: + // R/LCID MAC subheader | MAC SDU (UL CCCH 48 bits) + // { 0x34 | 0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06} (Random 6B sequence) + ASSERT_TRUE(payload.append(byte_buffer{0x34, 0x1e, 0x4f, 0xc0, 0x04, 0xa6, 0x06})); + // > MAC subPDU with PHR: + // R/LCID MAC subheader = R|R|LCID = 0x39 or LCID=57 + // MAC CE SE PHR = {0x27, 0x2f} + ASSERT_TRUE(payload.append(byte_buffer{0x39, 0x27, 0x2f})); + + // Send RX data indication to MAC UL + t_bench.send_rx_indication_msg(create_rx_data_indication(cell_idx, tc_rnti, std::move(payload))); + + // Note: For now PHRs are ignored in this scenario. + ASSERT_TRUE(t_bench.verify_no_sr_notification(tc_rnti)); +} + +TEST(mac_ul_processor, when_pdu_is_filled_with_zerosfor_existing_ue_then_the_mac_pdu_is_discarded) +{ + rnti_t ue1_rnti = to_rnti(0x4601); + du_ue_index_t ue1_idx = to_du_ue_index(1U); + du_cell_index_t cell_idx = to_du_cell_index(1U); + test_bench t_bench(ue1_rnti, ue1_idx, cell_idx); + + // Create PDU content. The PDU is made of several SDUs with size equal to UL-CCCH CE to be decodeable. + byte_buffer pdu; + // R/F/LCID MAC subheader | MAC CE UL-CCCH 64 + byte_buffer ul_ccch({0x0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + ASSERT_TRUE(pdu.append(ul_ccch)); + ASSERT_TRUE(pdu.append(ul_ccch)); + ASSERT_TRUE(pdu.append(ul_ccch)); + + // Send RX data indication to MAC UL. + t_bench.send_rx_indication_msg(ue1_rnti, pdu); + + // Test if notification sent to DU manager has been received and it is correct. + ASSERT_TRUE(t_bench.verify_no_ul_ccch_msg()); +} diff --git a/tests/unittests/ngap/ngap_error_indication_test.cpp b/tests/unittests/ngap/ngap_error_indication_test.cpp index f57ea46cc9..3d06b2d500 100644 --- a/tests/unittests/ngap/ngap_error_indication_test.cpp +++ b/tests/unittests/ngap/ngap_error_indication_test.cpp @@ -21,6 +21,7 @@ */ #include "ngap_test_helpers.h" +#include "srsran/asn1/ngap/ngap_pdu_contents.h" #include "srsran/support/test_utils.h" #include @@ -30,10 +31,9 @@ using namespace srs_cu_cp; class ngap_error_indication_test : public ngap_test { protected: - void start_procedure(const ue_index_t ue_index) + ue_index_t start_procedure() { - ASSERT_EQ(ngap->get_nof_ues(), 0); - create_ue(ue_index); + ue_index_t ue_index = create_ue(); // Inject DL NAS transport message from AMF run_dl_nas_transport(ue_index); @@ -43,6 +43,8 @@ class ngap_error_indication_test : public ngap_test // Inject Initial Context Setup Request run_inital_context_setup(ue_index); + + return ue_index; } bool was_error_indication_sent() const @@ -70,9 +72,7 @@ TEST_F(ngap_error_indication_test, TEST_F(ngap_error_indication_test, when_error_indication_message_for_existing_ue_received_message_is_logged) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); diff --git a/tests/unittests/ngap/ngap_handover_test.cpp b/tests/unittests/ngap/ngap_handover_test.cpp index a772b668b6..e2f7614850 100644 --- a/tests/unittests/ngap/ngap_handover_test.cpp +++ b/tests/unittests/ngap/ngap_handover_test.cpp @@ -22,6 +22,8 @@ #include "ngap_test_helpers.h" #include "srsran/ngap/ngap_handover.h" +#include "srsran/ran/cu_types.h" +#include "srsran/ran/lcid.h" #include "srsran/support/async/async_test_utils.h" #include @@ -53,13 +55,14 @@ TEST_F(ngap_test, when_ue_missing_then_handover_preparation_procedure_fails) TEST_F(ngap_test, when_source_gnb_handover_preparation_triggered_then_ho_command_received) { // Setup UE context - ue_index_t ue_index = uint_to_ue_index(0); - create_ue(ue_index); + ue_index_t ue_index = create_ue(); run_dl_nas_transport(ue_index); // needed to allocate AMF UE id. - ngap_ue_source_handover_context ho_context; - ho_context.pdu_sessions = {{uint_to_pdu_session_id(1), {qos_flow_id_t{}}}}; // manually set existing PDU sessions - rrc_ue_notifier.set_handover_context(ho_context); + // Manually add existing PDU sessions to UP manager + add_pdu_session_to_up_manager(ue_index, uint_to_pdu_session_id(1), uint_to_drb_id(0), uint_to_qos_flow_id(0)); + + auto& ue = test_ues.at(ue_index); + ue.rrc_ue_notifier.set_ho_preparation_message({}); ngap_handover_preparation_request request = {}; request.ue_index = ue_index; @@ -79,7 +82,6 @@ TEST_F(ngap_test, when_source_gnb_handover_preparation_triggered_then_ho_command ASSERT_FALSE(t.ready()); // Inject Handover Command - auto& ue = test_ues.at(ue_index); ngap_message ho_cmd = generate_valid_handover_command(ue.amf_ue_id.value(), ue.ran_ue_id.value()); ngap->handle_message(ho_cmd); diff --git a/tests/unittests/ngap/ngap_nas_message_test.cpp b/tests/unittests/ngap/ngap_nas_message_test.cpp index 4a82b5e91f..7b96be254c 100644 --- a/tests/unittests/ngap/ngap_nas_message_test.cpp +++ b/tests/unittests/ngap/ngap_nas_message_test.cpp @@ -21,6 +21,7 @@ */ #include "ngap_test_helpers.h" +#include "srsran/ran/cause.h" #include "srsran/support/async/async_test_utils.h" #include "srsran/support/test_utils.h" #include @@ -31,24 +32,24 @@ using namespace srs_cu_cp; class ngap_nas_message_routine_test : public ngap_test { protected: - void start_procedure(const ue_index_t ue_index) - { - ASSERT_EQ(ngap->get_nof_ues(), 0); - create_ue(ue_index); - } + ue_index_t start_procedure() { return create_ue(); } - void start_dl_nas_procedure(ue_index_t ue_index) + ue_index_t start_dl_nas_procedure() { - ASSERT_EQ(ngap->get_nof_ues(), 0); - create_ue(ue_index); + ue_index_t ue_index = create_ue(); // Inject DL NAS transport message from AMF run_dl_nas_transport(ue_index); + + return ue_index; } - bool was_dl_nas_transport_forwarded() const { return rrc_ue_notifier.last_nas_pdu.length() == nas_pdu_len; } + bool was_dl_nas_transport_forwarded(const test_ue& ue) const + { + return ue.rrc_ue_notifier.last_nas_pdu.length() == nas_pdu_len; + } - bool was_dl_nas_transport_dropped() const { return rrc_ue_notifier.last_nas_pdu.length() == 0; } + bool was_dl_nas_transport_dropped(const test_ue& ue) const { return ue.rrc_ue_notifier.last_nas_pdu.empty(); } bool was_ul_nas_transport_forwarded() const { @@ -69,22 +70,45 @@ TEST_F(ngap_nas_message_routine_test, when_initial_ue_message_is_received_then_n ASSERT_EQ(ngap->get_nof_ues(), 0); // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + this->start_procedure(); // check that initial UE message is sent to AMF and that UE objects has been created ASSERT_EQ(msg_notifier.last_ngap_msg.pdu.type().value, asn1::ngap::ngap_pdu_c::types_opts::init_msg); + ASSERT_EQ(msg_notifier.last_ngap_msg.pdu.init_msg().value.type(), + asn1::ngap::ngap_elem_procs_o::init_msg_c::types_opts::init_ue_msg); ASSERT_EQ(ngap->get_nof_ues(), 1); } +/// Initial UE message tests +TEST_F(ngap_nas_message_routine_test, when_initial_context_setup_request_is_not_received_then_ue_is_released) +{ + ASSERT_EQ(ngap->get_nof_ues(), 0); + + // Test preamble + this->start_procedure(); + + // check that initial UE message is sent to AMF and that UE objects has been created + ASSERT_EQ(msg_notifier.last_ngap_msg.pdu.type().value, asn1::ngap::ngap_pdu_c::types_opts::init_msg); + ASSERT_EQ(msg_notifier.last_ngap_msg.pdu.init_msg().value.type(), + asn1::ngap::ngap_elem_procs_o::init_msg_c::types_opts::init_ue_msg); + ASSERT_EQ(ngap->get_nof_ues(), 1); + + // tick timers + // Status: NGAP does not receive new Initial Context Setup Request until ue_context_setup_timer has ended. + for (unsigned msec_elapsed = 0; msec_elapsed < cfg.ue_context_setup_timeout_s.count() * 1000; ++msec_elapsed) { + this->tick(); + } + + // check that UE release was requested + ASSERT_NE(du_processor_notifier->last_command.ue_index, ue_index_t::invalid); + ASSERT_EQ(du_processor_notifier->last_command.cause, cause_t{cause_nas_t::unspecified}); +} + /// Test DL NAS transport handling TEST_F(ngap_nas_message_routine_test, when_ue_present_dl_nas_transport_is_forwarded) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); ue.amf_ue_id = uint_to_amf_ue_id( @@ -95,22 +119,19 @@ TEST_F(ngap_nas_message_routine_test, when_ue_present_dl_nas_transport_is_forwar ngap->handle_message(dl_nas_transport); // Check that RRC notifier was called - ASSERT_TRUE(was_dl_nas_transport_forwarded()); + ASSERT_TRUE(was_dl_nas_transport_forwarded(ue)); } TEST_F(ngap_nas_message_routine_test, when_no_ue_present_dl_nas_transport_is_dropped_and_error_indication_is_sent) { // Inject DL NAS transport message from AMF ngap_message dl_nas_transport = generate_downlink_nas_transport_message( - uint_to_amf_ue_id( test_rgen::uniform_int(amf_ue_id_to_uint(amf_ue_id_t::min), amf_ue_id_to_uint(amf_ue_id_t::max))), uint_to_ran_ue_id( test_rgen::uniform_int(ran_ue_id_to_uint(ran_ue_id_t::min), ran_ue_id_to_uint(ran_ue_id_t::max)))); ngap->handle_message(dl_nas_transport); - // Check that no message has been sent to RRC - ASSERT_TRUE(was_dl_nas_transport_dropped()); // Check that Error Indication has been sent to AMF ASSERT_TRUE(was_error_indication_sent()); } @@ -119,11 +140,9 @@ TEST_F(ngap_nas_message_routine_test, when_no_ue_present_dl_nas_transport_is_dro TEST_F(ngap_nas_message_routine_test, when_ue_present_and_amf_set_ul_nas_transport_is_forwared) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_dl_nas_procedure(ue_index); + ue_index_t ue_index = this->start_dl_nas_procedure(); - ngap_ul_nas_transport_message ul_nas_transport = generate_ul_nas_transport_message(ue_index); + cu_cp_ul_nas_transport ul_nas_transport = generate_ul_nas_transport_message(ue_index); ngap->handle_ul_nas_transport_message(ul_nas_transport); // Check that AMF notifier was called with right type @@ -134,9 +153,7 @@ TEST_F(ngap_nas_message_routine_test, when_ue_present_and_amf_set_ul_nas_transpo TEST_F(ngap_nas_message_routine_test, when_amf_ue_id_is_max_size_then_its_not_cropped) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); ue.amf_ue_id = amf_ue_id_t::max; @@ -146,12 +163,9 @@ TEST_F(ngap_nas_message_routine_test, when_amf_ue_id_is_max_size_then_its_not_cr ngap->handle_message(dl_nas_transport); // Check that RRC notifier was called - ASSERT_TRUE(was_dl_nas_transport_forwarded()); - - ngap_ue* ngap_ue = ue_mng.find_ngap_ue(ue_index); - ASSERT_EQ(ngap_ue->get_amf_ue_id(), ue.amf_ue_id); + ASSERT_TRUE(was_dl_nas_transport_forwarded(ue)); - ngap_ul_nas_transport_message ul_nas_transport = generate_ul_nas_transport_message(ue_index); + cu_cp_ul_nas_transport ul_nas_transport = generate_ul_nas_transport_message(ue_index); ngap->handle_ul_nas_transport_message(ul_nas_transport); // Check that AMF notifier was called with right type diff --git a/tests/unittests/ngap/ngap_pdu_session_resource_release_procedure_test.cpp b/tests/unittests/ngap/ngap_pdu_session_resource_release_procedure_test.cpp index b93b1655bb..fd7cf82a79 100644 --- a/tests/unittests/ngap/ngap_pdu_session_resource_release_procedure_test.cpp +++ b/tests/unittests/ngap/ngap_pdu_session_resource_release_procedure_test.cpp @@ -31,10 +31,9 @@ using namespace srs_cu_cp; class ngap_pdu_session_resource_release_procedure_test : public ngap_test { protected: - void start_procedure(const ue_index_t ue_index, const pdu_session_id_t pdu_session_id) + ue_index_t start_procedure(const pdu_session_id_t pdu_session_id) { - ASSERT_EQ(ngap->get_nof_ues(), 0); - create_ue(ue_index); + ue_index_t ue_index = create_ue(); // Inject DL NAS transport message from AMF run_dl_nas_transport(ue_index); @@ -47,6 +46,8 @@ class ngap_pdu_session_resource_release_procedure_test : public ngap_test // Inject PDU Session Resource Setup request run_pdu_session_resource_setup(ue_index, pdu_session_id); + + return ue_index; } bool was_conversion_successful(ngap_message pdu_session_resource_release_command, @@ -55,9 +56,9 @@ class ngap_pdu_session_resource_release_procedure_test : public ngap_test bool test_1 = pdu_session_resource_release_command.pdu.init_msg() .value.pdu_session_res_release_cmd() ->pdu_session_res_to_release_list_rel_cmd.size() == - du_processor_notifier.last_release_command.pdu_session_res_to_release_list_rel_cmd.size(); + du_processor_notifier->last_release_command.pdu_session_res_to_release_list_rel_cmd.size(); - bool test_2 = du_processor_notifier.last_release_command.pdu_session_res_to_release_list_rel_cmd[pdu_session_id] + bool test_2 = du_processor_notifier->last_release_command.pdu_session_res_to_release_list_rel_cmd[pdu_session_id] .pdu_session_id == pdu_session_id; return test_1 && test_2; @@ -90,13 +91,10 @@ TEST_F(ngap_pdu_session_resource_release_procedure_test, when_valid_pdu_session_resource_release_command_received_then_pdu_session_release_succeeds) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - pdu_session_id_t pdu_session_id = uint_to_pdu_session_id(test_rgen::uniform_int( pdu_session_id_to_uint(pdu_session_id_t::min), pdu_session_id_to_uint(pdu_session_id_t::max))); - this->start_procedure(ue_index, pdu_session_id); + ue_index_t ue_index = this->start_procedure(pdu_session_id); auto& ue = test_ues.at(ue_index); @@ -116,13 +114,10 @@ TEST_F(ngap_pdu_session_resource_release_procedure_test, when_pdu_session_resource_setup_request_received_after_release_command_then_pdu_session_setup_succeeds) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - pdu_session_id_t pdu_session_id = uint_to_pdu_session_id(test_rgen::uniform_int( pdu_session_id_to_uint(pdu_session_id_t::min), pdu_session_id_to_uint(pdu_session_id_t::max))); - this->start_procedure(ue_index, pdu_session_id); + ue_index_t ue_index = this->start_procedure(pdu_session_id); auto& ue = test_ues.at(ue_index); diff --git a/tests/unittests/ngap/ngap_pdu_session_resource_setup_procedure_test.cpp b/tests/unittests/ngap/ngap_pdu_session_resource_setup_procedure_test.cpp index 9edee8dc6f..b144a3296a 100644 --- a/tests/unittests/ngap/ngap_pdu_session_resource_setup_procedure_test.cpp +++ b/tests/unittests/ngap/ngap_pdu_session_resource_setup_procedure_test.cpp @@ -31,10 +31,9 @@ using namespace srs_cu_cp; class ngap_pdu_session_resource_setup_procedure_test : public ngap_test { protected: - void start_procedure(const ue_index_t ue_index) + ue_index_t start_procedure() { - ASSERT_EQ(ngap->get_nof_ues(), 0); - create_ue(ue_index); + ue_index_t ue_index = create_ue(); // Inject DL NAS transport message from AMF run_dl_nas_transport(ue_index); @@ -44,6 +43,8 @@ class ngap_pdu_session_resource_setup_procedure_test : public ngap_test // Inject Initial Context Setup Request run_inital_context_setup(ue_index); + + return ue_index; } bool was_conversion_successful(ngap_message pdu_session_resource_setup_request, pdu_session_id_t pdu_session_id) const @@ -51,10 +52,10 @@ class ngap_pdu_session_resource_setup_procedure_test : public ngap_test bool test_1 = pdu_session_resource_setup_request.pdu.init_msg() .value.pdu_session_res_setup_request() ->pdu_session_res_setup_list_su_req.size() == - du_processor_notifier.last_request.pdu_session_res_setup_items.size(); + du_processor_notifier->last_request.pdu_session_res_setup_items.size(); bool test_2 = - du_processor_notifier.last_request.pdu_session_res_setup_items[pdu_session_id].pdu_session_type == "ipv4"; + du_processor_notifier->last_request.pdu_session_res_setup_items[pdu_session_id].pdu_session_type == "ipv4"; return test_1 && test_2; } @@ -104,9 +105,7 @@ TEST_F(ngap_pdu_session_resource_setup_procedure_test, when_valid_pdu_session_resource_setup_request_received_then_pdu_session_setup_succeeds) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); // Inject PDU Session Resource Setup Request pdu_session_id_t pdu_session_id = uint_to_pdu_session_id(test_rgen::uniform_int( @@ -130,9 +129,7 @@ TEST_F(ngap_pdu_session_resource_setup_procedure_test, when_invalid_pdu_session_resource_setup_request_received_then_pdu_session_setup_failed) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); @@ -149,18 +146,15 @@ TEST_F(ngap_pdu_session_resource_setup_procedure_test, TEST_F(ngap_pdu_session_resource_setup_procedure_test, when_security_not_enabled_then_pdu_session_setup_failed) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); + auto& ue = test_ues.at(ue_index); - rrc_ue_notifier.set_security_enabled(false); + ue.rrc_ue_notifier.set_security_enabled(false); // Inject PDU Session Resource Setup Request pdu_session_id_t pdu_session_id = uint_to_pdu_session_id(test_rgen::uniform_int( pdu_session_id_to_uint(pdu_session_id_t::min), pdu_session_id_to_uint(pdu_session_id_t::max))); - auto& ue = test_ues.at(ue_index); - ngap_message pdu_session_resource_setup_request = generate_valid_pdu_session_resource_setup_request_message( ue.amf_ue_id.value(), ue.ran_ue_id.value(), pdu_session_id); ngap->handle_message(pdu_session_resource_setup_request); diff --git a/tests/unittests/ngap/ngap_test_helpers.cpp b/tests/unittests/ngap/ngap_test_helpers.cpp index da6975e8c7..70cef49053 100644 --- a/tests/unittests/ngap/ngap_test_helpers.cpp +++ b/tests/unittests/ngap/ngap_test_helpers.cpp @@ -21,8 +21,12 @@ */ #include "ngap_test_helpers.h" +#include "srsran/ran/cu_types.h" +#include "srsran/ran/lcid.h" #include "srsran/support/async/async_test_utils.h" #include "srsran/support/test_utils.h" +#include +#include using namespace srsran; using namespace srs_cu_cp; @@ -40,8 +44,12 @@ ngap_test::ngap_test() : ngap_ue_task_scheduler(timers, ctrl_worker) s_nssai_t slice_cfg; slice_cfg.sst = 1; cfg.slice_configurations.push_back(slice_cfg); + cfg.ue_context_setup_timeout_s = std::chrono::seconds(2); ngap = create_ngap(cfg, cu_cp_paging_notifier, ngap_ue_task_scheduler, ue_mng, msg_notifier, ctrl_worker); + + du_processor_notifier = + std::make_unique(ngap->get_ngap_ue_context_removal_handler()); } ngap_test::~ngap_test() @@ -50,23 +58,35 @@ ngap_test::~ngap_test() srslog::flush(); } -void ngap_test::create_ue(ue_index_t ue_index) +ue_index_t ngap_test::create_ue(rnti_t rnti) { + // Create UE in UE manager + ue_index_t ue_index = ue_mng.allocate_new_ue_index(uint_to_du_index(0)); + auto* ue = ue_mng.add_ue(ue_index, MIN_PCI, rnti); + if (ue == nullptr) { + test_logger.error("Failed to create UE with pci={} and rnti={}", MIN_PCI, rnti_t::MIN_CRNTI); + return ue_index_t::invalid; + } + // Inject UE creation at NGAP - ngap->create_ngap_ue(ue_index, rrc_ue_notifier, rrc_ue_notifier, du_processor_notifier); + test_ues.emplace(ue_index, test_ue(ue_index)); + test_ue& new_test_ue = test_ues.at(ue_index); + ngap->create_ngap_ue(ue_index, new_test_ue.rrc_ue_notifier, new_test_ue.rrc_ue_notifier, *du_processor_notifier); // generate and inject valid initial ue message - ngap_initial_ue_message msg = generate_initial_ue_message(ue_index); + cu_cp_initial_ue_message msg = generate_initial_ue_message(ue_index); ngap->handle_initial_ue_message(msg); - test_ues.emplace(ue_index, ue_index); - test_ues.at(ue_index).ran_ue_id = + new_test_ue.ran_ue_id = uint_to_ran_ue_id(msg_notifier.last_ngap_msg.pdu.init_msg().value.init_ue_msg()->ran_ue_ngap_id); + + return ue_index; } void ngap_test::run_dl_nas_transport(ue_index_t ue_index) { auto& ue = test_ues.at(ue_index); + ue.amf_ue_id = uint_to_amf_ue_id(test_rgen::uniform_int(16, 128)); ue.amf_ue_id = uint_to_amf_ue_id( test_rgen::uniform_int(amf_ue_id_to_uint(amf_ue_id_t::min), amf_ue_id_to_uint(amf_ue_id_t::max))); @@ -76,7 +96,7 @@ void ngap_test::run_dl_nas_transport(ue_index_t ue_index) void ngap_test::run_ul_nas_transport(ue_index_t ue_index) { - ngap_ul_nas_transport_message ul_nas_transport = generate_ul_nas_transport_message(ue_index); + cu_cp_ul_nas_transport ul_nas_transport = generate_ul_nas_transport_message(ue_index); ngap->handle_ul_nas_transport_message(ul_nas_transport); } @@ -98,6 +118,23 @@ void ngap_test::run_pdu_session_resource_setup(ue_index_t ue_index, pdu_session_ ngap->handle_message(pdu_session_resource_setup_request); } +void ngap_test::add_pdu_session_to_up_manager(ue_index_t ue_index, + pdu_session_id_t pdu_session_id, + drb_id_t drb_id, + qos_flow_id_t qos_flow_id) +{ + auto& up_mng = ue_mng.find_ngap_ue(ue_index)->get_up_resource_manager(); + up_config_update_result result; + up_pdu_session_context_update ctxt_update{pdu_session_id}; + std::map qos_flows; + qos_flows[qos_flow_id] = {qos_flow_id, {}}; + ctxt_update.drb_to_add[drb_id] = {drb_id, pdu_session_id, {}, false, {}, {}, qos_flows, {}, {}, {}}; + + result.pdu_sessions_added_list.push_back(ctxt_update); + + up_mng.apply_config_update(result); +} + void ngap_test::tick() { timers.tick(); diff --git a/tests/unittests/ngap/ngap_test_helpers.h b/tests/unittests/ngap/ngap_test_helpers.h index 495d80241f..5e8276fa86 100644 --- a/tests/unittests/ngap/ngap_test_helpers.h +++ b/tests/unittests/ngap/ngap_test_helpers.h @@ -47,13 +47,15 @@ class ngap_test : public ::testing::Test ue_index_t ue_index = ue_index_t::invalid; optional amf_ue_id; optional ran_ue_id; + + dummy_ngap_rrc_ue_notifier rrc_ue_notifier; }; ngap_test(); ~ngap_test() override; /// \brief Helper method to successfully create UE instance in NGAP. - void create_ue(ue_index_t ue_index); + ue_index_t create_ue(rnti_t rnti = rnti_t::MIN_CRNTI); /// \brief Helper method to successfully run DL NAS transport in NGAP. void run_dl_nas_transport(ue_index_t ue_index); @@ -67,6 +69,12 @@ class ngap_test : public ::testing::Test /// \brief Helper method to successfully run PDU Session Resource Setup in NGAP void run_pdu_session_resource_setup(ue_index_t ue_index, pdu_session_id_t pdu_session_id); + // Manually add existing PDU sessions to UP manager + void add_pdu_session_to_up_manager(ue_index_t ue_index, + pdu_session_id_t pdu_session_id, + drb_id_t drb_id, + qos_flow_id_t qos_flow_id); + /// \brief Manually tick timers. void tick(); @@ -75,16 +83,15 @@ class ngap_test : public ::testing::Test std::unordered_map test_ues; - ngap_configuration cfg; - timer_manager timers; - dummy_ngap_ue_manager ue_mng; - dummy_ngap_amf_notifier msg_notifier; - dummy_ngap_rrc_ue_notifier rrc_ue_notifier; - dummy_ngap_du_processor_notifier du_processor_notifier; - dummy_ngap_cu_cp_paging_notifier cu_cp_paging_notifier; - dummy_ngap_ue_task_scheduler ngap_ue_task_scheduler; - manual_task_worker ctrl_worker{128}; - std::unique_ptr ngap; + ngap_configuration cfg; + timer_manager timers; + ue_manager ue_mng{{}, {}}; + dummy_ngap_amf_notifier msg_notifier; + std::unique_ptr du_processor_notifier; + dummy_ngap_cu_cp_paging_notifier cu_cp_paging_notifier; + dummy_ngap_ue_task_scheduler ngap_ue_task_scheduler; + manual_task_worker ctrl_worker{128}; + std::unique_ptr ngap; }; } // namespace srs_cu_cp diff --git a/tests/unittests/ngap/ngap_test_messages.cpp b/tests/unittests/ngap/ngap_test_messages.cpp index d4de48d38f..e2f263beb8 100644 --- a/tests/unittests/ngap/ngap_test_messages.cpp +++ b/tests/unittests/ngap/ngap_test_messages.cpp @@ -118,13 +118,16 @@ ngap_message srsran::srs_cu_cp::generate_ng_setup_failure_with_time_to_wait(time return ng_setup_failure; } -ngap_initial_ue_message srsran::srs_cu_cp::generate_initial_ue_message(ue_index_t ue_index) +cu_cp_initial_ue_message srsran::srs_cu_cp::generate_initial_ue_message(ue_index_t ue_index) { - ngap_initial_ue_message msg = {}; - msg.ue_index = ue_index; + cu_cp_initial_ue_message msg = {}; + msg.ue_index = ue_index; msg.nas_pdu.resize(nas_pdu_len); - msg.establishment_cause.value = rrc_establishment_cause_opts::mo_sig; - msg.tac = 7; + msg.establishment_cause = static_cast(rrc_establishment_cause_opts::mo_sig); + msg.user_location_info.nr_cgi.plmn_hex = "00f110"; + msg.user_location_info.nr_cgi.nci = 6576; + msg.user_location_info.tai.plmn_id = "00f110"; + msg.user_location_info.tai.tac = 7; return msg; } @@ -149,11 +152,15 @@ ngap_message srsran::srs_cu_cp::generate_downlink_nas_transport_message(amf_ue_i return dl_nas_transport; } -ngap_ul_nas_transport_message srsran::srs_cu_cp::generate_ul_nas_transport_message(ue_index_t ue_index) +cu_cp_ul_nas_transport srsran::srs_cu_cp::generate_ul_nas_transport_message(ue_index_t ue_index) { - ngap_ul_nas_transport_message ul_nas_transport = {}; - ul_nas_transport.ue_index = ue_index; + cu_cp_ul_nas_transport ul_nas_transport = {}; + ul_nas_transport.ue_index = ue_index; ul_nas_transport.nas_pdu.resize(nas_pdu_len); + ul_nas_transport.user_location_info.nr_cgi.plmn_hex = "00f110"; + ul_nas_transport.user_location_info.nr_cgi.nci = 6576; + ul_nas_transport.user_location_info.tai.plmn_id = "00f110"; + ul_nas_transport.user_location_info.tai.tac = 7; return ul_nas_transport; } diff --git a/tests/unittests/ngap/ngap_test_messages.h b/tests/unittests/ngap/ngap_test_messages.h index 730c0e0b2d..ce0cb82932 100644 --- a/tests/unittests/ngap/ngap_test_messages.h +++ b/tests/unittests/ngap/ngap_test_messages.h @@ -101,14 +101,14 @@ ngap_message generate_ng_setup_failure_with_time_to_wait(asn1::ngap::time_to_wai const uint32_t nas_pdu_len = 4; // Dummy length used for testing (content is not important) /// \brief Generate a dummy Initial UE Message. -ngap_initial_ue_message generate_initial_ue_message(ue_index_t ue_index); +cu_cp_initial_ue_message generate_initial_ue_message(ue_index_t ue_index); /// \brief Generate a dummy DL NAS Transport Message. ngap_message generate_downlink_nas_transport_message(amf_ue_id_t amf_ue_id, ran_ue_id_t ran_ue_id, byte_buffer nas_pdu = {}); /// \brief Generate a dummy UL NAS Transport Message. -ngap_ul_nas_transport_message generate_ul_nas_transport_message(ue_index_t ue_index); +cu_cp_ul_nas_transport generate_ul_nas_transport_message(ue_index_t ue_index); /// \brief Generate a dummy UL NAS Transport Message. ngap_message generate_uplink_nas_transport_message(amf_ue_id_t amf_ue_id, ran_ue_id_t ran_ue_id); diff --git a/tests/unittests/ngap/ngap_ue_context_management_procedure_test.cpp b/tests/unittests/ngap/ngap_ue_context_management_procedure_test.cpp index efbcbe554b..65d0fac317 100644 --- a/tests/unittests/ngap/ngap_ue_context_management_procedure_test.cpp +++ b/tests/unittests/ngap/ngap_ue_context_management_procedure_test.cpp @@ -31,16 +31,17 @@ using namespace srs_cu_cp; class ngap_ue_context_management_procedure_test : public ngap_test { protected: - void start_procedure(const ue_index_t ue_index) + ue_index_t start_procedure() { - ASSERT_EQ(ngap->get_nof_ues(), 0); - create_ue(ue_index); + ue_index_t ue_index = create_ue(); // Inject DL NAS transport message from AMF run_dl_nas_transport(ue_index); // Inject UL NAS transport message from RRC run_ul_nas_transport(ue_index); + + return ue_index; } bool was_initial_context_setup_response_sent() const @@ -77,6 +78,9 @@ class ngap_ue_context_management_procedure_test : public ngap_test bool was_ue_context_release_request_sent() const { + if (msg_notifier.last_ngap_msg.pdu.type() == asn1::ngap::ngap_pdu_c::types_opts::nulltype) { + return false; + } return msg_notifier.last_ngap_msg.pdu.init_msg().value.type() == asn1::ngap::ngap_elem_procs_o::init_msg_c::types_opts::ue_context_release_request; } @@ -90,15 +94,15 @@ class ngap_ue_context_management_procedure_test : public ngap_test bool was_ue_added() const { return ngap->get_nof_ues() == 1; } bool was_ue_removed() const { return ngap->get_nof_ues() == 0; } + + void clear_last_received_msg() { msg_notifier.last_ngap_msg = {}; } }; /// Test Initial Context Setup Request TEST_F(ngap_ue_context_management_procedure_test, when_valid_initial_context_setup_request_received_then_response_send) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); @@ -118,9 +122,7 @@ TEST_F(ngap_ue_context_management_procedure_test, when_initial_context_setup_request_with_pdu_session_received_then_response_send) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); @@ -142,21 +144,13 @@ TEST_F(ngap_ue_context_management_procedure_test, when_new_amf_ue_id_is_sent_in_initial_context_setup_request_received_then_id_is_updated) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); // Get "first" AMF UE ID received amf_ue_id_t old_id = ue.amf_ue_id.value(); - // Lookup UE in UE manager - ngap_ue* ngap_ue = ue_mng.find_ngap_ue(ue_index); - - // Check that UE manager has the same AMF UE ID - ASSERT_EQ(ngap_ue->get_amf_ue_id(), old_id); - // randomly generate new ID assigned by core amf_ue_id_t new_id = old_id; while (new_id == old_id) { @@ -174,18 +168,13 @@ TEST_F(ngap_ue_context_management_procedure_test, ASSERT_TRUE(was_initial_context_setup_response_sent()); ASSERT_TRUE(was_ue_added()); - - // Check that UE has new AMF UE ID - ASSERT_EQ(ngap_ue->get_amf_ue_id(), new_id); } /// Test invalid Initial Context Setup Request TEST_F(ngap_ue_context_management_procedure_test, when_invalid_initial_context_setup_request_received_then_failure_sent) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); @@ -196,8 +185,6 @@ TEST_F(ngap_ue_context_management_procedure_test, when_invalid_initial_context_s // Check that AMF notifier was called with right type ASSERT_TRUE(was_initial_context_setup_failure_sent()); - - ASSERT_TRUE(was_ue_removed()); } /// Test invalid Initial Context Setup Request with PDUSessionResourceSetupListCxtReq @@ -205,9 +192,7 @@ TEST_F(ngap_ue_context_management_procedure_test, when_invalid_initial_context_setup_request_with_pdu_session_received_then_failure_sent) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); @@ -219,8 +204,6 @@ TEST_F(ngap_ue_context_management_procedure_test, // Check that AMF notifier was called with right type ASSERT_TRUE(was_initial_context_setup_failure_sent()); - ASSERT_TRUE(was_ue_removed()); - ASSERT_TRUE(was_pdu_session_resource_setup_unsuccessful()); } @@ -229,9 +212,7 @@ TEST_F(ngap_ue_context_management_procedure_test, when_ue_context_release_command_with_amf_ue_ngap_id_received_then_ue_is_released_and_release_complete_is_sent) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); @@ -251,14 +232,35 @@ TEST_F(ngap_ue_context_management_procedure_test, ASSERT_TRUE(was_ue_removed()); } +/// Initial UE message tests +TEST_F(ngap_ue_context_management_procedure_test, + when_release_command_after_initial_ue_message_is_received_then_ue_is_released) +{ + ASSERT_EQ(ngap->get_nof_ues(), 0); + + // Test preamble + ue_index_t ue_index = create_ue(); + + auto& ue = test_ues.at(ue_index); + + // Inject DL NAS transport message from AMF + run_dl_nas_transport(ue_index); + + // Inject UE Context Release Command + ngap_message ue_context_release_cmd = + generate_valid_ue_context_release_command_with_amf_ue_ngap_id(ue.amf_ue_id.value()); + ngap->handle_message(ue_context_release_cmd); + + ASSERT_TRUE(was_ue_context_release_complete_sent()); + ASSERT_TRUE(was_ue_removed()); +} + /// Test successful UE context release TEST_F(ngap_ue_context_management_procedure_test, when_ue_context_release_command_with_ue_ngap_id_pair_received_then_ue_is_released_and_release_complete_is_sent) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); @@ -283,9 +285,7 @@ TEST_F(ngap_ue_context_management_procedure_test, when_ue_context_release_command_for_unknown_ue_received_then_ue_is_not_released_and_release_complete_is_not_sent) { // Test preamble - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - this->start_procedure(ue_index); + ue_index_t ue_index = this->start_procedure(); auto& ue = test_ues.at(ue_index); @@ -311,14 +311,89 @@ TEST_F(ngap_ue_context_management_procedure_test, when_ue_context_release_request_is_received_but_no_amf_ue_ngap_id_is_set_then_request_is_ignored) { // Test preamble - Only create UE but do not have DL traffic from the AMF. - ue_index_t ue_index = uint_to_ue_index( - test_rgen::uniform_int(ue_index_to_uint(ue_index_t::min), ue_index_to_uint(ue_index_t::max))); - create_ue(ue_index); + ue_index_t ue_index = create_ue(); + + // Trigger UE context release request. + cu_cp_ue_context_release_request release_request; + release_request.ue_index = ue_index; + ngap->handle_ue_context_release_request(release_request); + + ASSERT_FALSE(was_ue_context_release_request_sent()); +} + +/// Test UE context release request is not sent multiple times for same UE. +TEST_F(ngap_ue_context_management_procedure_test, + when_ue_context_release_request_is_received_multiple_times_ngap_message_is_not_sent_more_than_once) +{ + // Test preamble + ue_index_t ue_index = this->start_procedure(); + + auto& ue = test_ues.at(ue_index); + + // Inject Initial Context Setup Request + ngap_message init_context_setup_request = + generate_valid_initial_context_setup_request_message(ue.amf_ue_id.value(), ue.ran_ue_id.value()); + ngap->handle_message(init_context_setup_request); + + ASSERT_TRUE(was_ue_added()); // Trigger UE context release request. cu_cp_ue_context_release_request release_request; release_request.ue_index = ue_index; ngap->handle_ue_context_release_request(release_request); + ASSERT_TRUE(was_ue_context_release_request_sent()); + + // Trigger 2nd UE context release request. + clear_last_received_msg(); + ngap->handle_ue_context_release_request(release_request); ASSERT_FALSE(was_ue_context_release_request_sent()); -} \ No newline at end of file +} + +/// Test DL NAS transport after transfering UE IDs/context. +TEST_F(ngap_ue_context_management_procedure_test, when_ue_context_is_tranfered_amf_ue_id_is_updated) +{ + // Normal test preamble to get UE created. + ue_index_t ue_index = this->start_procedure(); + auto& ue = test_ues.at(ue_index); + ASSERT_NE(ue.ue_index, ue_index_t::invalid); + + // Get AMF UE ID + amf_ue_id_t amf_id = ue.amf_ue_id.value(); + ASSERT_NE(amf_id, amf_ue_id_t::invalid); + + ran_ue_id_t ran_id = ue.ran_ue_id.value(); + ASSERT_NE(ran_id, ran_ue_id_t::invalid); + + // Clear NAS PDU. + ue.rrc_ue_notifier.last_nas_pdu.clear(); + ASSERT_TRUE(ue.rrc_ue_notifier.last_nas_pdu.empty()); + + // Inject new DL NAS transport from core. + ngap_message dl_nas_transport = generate_downlink_nas_transport_message(amf_id, ue.ran_ue_id.value()); + ngap->handle_message(dl_nas_transport); + + // Check NAS PDU has been passed to RRC. + ASSERT_FALSE(ue.rrc_ue_notifier.last_nas_pdu.empty()); + + // Clear PDU again. + ue.rrc_ue_notifier.last_nas_pdu.clear(); + + // Create new UE object (with own RRC UE notifier). + ue_index_t target_ue_index = create_ue(rnti_t::MAX_CRNTI); + ASSERT_NE(target_ue_index, ue_index_t::invalid); + auto& target_ue = test_ues.at(target_ue_index); + ASSERT_TRUE(target_ue.rrc_ue_notifier.last_nas_pdu.empty()); + + // Transfer NGAP UE context to new target UE. + ngap->update_ue_index(target_ue_index, ue_index); + + // Inject NAS message again. + ngap->handle_message(dl_nas_transport); + + // Check that RRC notifier of initial UE has not been called. + ASSERT_TRUE(ue.rrc_ue_notifier.last_nas_pdu.empty()); + + // Verify that RRC notifier of target UE has indeed benn called. + ASSERT_FALSE(target_ue.rrc_ue_notifier.last_nas_pdu.empty()); +} diff --git a/tests/unittests/ngap/test_helpers.h b/tests/unittests/ngap/test_helpers.h index a1b7308417..5c04d9a1ab 100644 --- a/tests/unittests/ngap/test_helpers.h +++ b/tests/unittests/ngap/test_helpers.h @@ -23,10 +23,12 @@ #pragma once #include "ngap_test_messages.h" +#include "srsran/adt/byte_buffer.h" #include "srsran/cu_cp/cu_cp_types.h" -#include "srsran/cu_cp/ue_manager.h" #include "srsran/pcap/pcap.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/rrc/rrc_ue.h" +#include "srsran/security/security.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include #include @@ -46,235 +48,9 @@ struct dummy_ngap_ue_task_scheduler : public ngap_ue_task_scheduler { void tick_timer() { timer_db.tick(); } private: - async_task_sequencer ctrl_loop{16}; - timer_manager& timer_db; - task_executor& exec; -}; - -struct dummy_ngap_ue : public ngap_ue { -public: - dummy_ngap_ue(ue_index_t ue_index_) : ue_index(ue_index_) {} - - ue_index_t get_ue_index() override { return ue_index; } - - void set_aggregate_maximum_bit_rate_dl(uint64_t aggregate_maximum_bit_rate_dl_) override - { - aggregate_maximum_bit_rate_dl = aggregate_maximum_bit_rate_dl_; - } - - ngap_rrc_ue_pdu_notifier& get_rrc_ue_pdu_notifier() override { return *rrc_ue_pdu_notifier; } - ngap_rrc_ue_control_notifier& get_rrc_ue_control_notifier() override { return *rrc_ue_ctrl_notifier; } - ngap_du_processor_control_notifier& get_du_processor_control_notifier() override - { - return *du_processor_ctrl_notifier; - } - - amf_ue_id_t get_amf_ue_id() override { return amf_ue_id; } - ran_ue_id_t get_ran_ue_id() override { return ran_ue_id; } - - uint64_t get_aggregate_maximum_bit_rate_dl() override { return aggregate_maximum_bit_rate_dl; } - - bool du_ue_created = false; - bool ngap_ue_created = false; - - void set_ran_ue_id(ran_ue_id_t ran_ue_id_) { ran_ue_id = ran_ue_id_; } - - void set_rrc_ue_pdu_notifier(ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_) - { - rrc_ue_pdu_notifier = &rrc_ue_pdu_notifier_; - } - - void set_rrc_ue_ctrl_notifier(ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier_) - { - rrc_ue_ctrl_notifier = &rrc_ue_ctrl_notifier_; - } - - void set_du_processor_ctrl_notifier(ngap_du_processor_control_notifier& du_processor_ctrl_notifier_) - { - du_processor_ctrl_notifier = &du_processor_ctrl_notifier_; - } - - /// \brief Set the AMF UE ID in the UE. - /// \param[in] amf_ue_id The AMF UE ID to set. - void set_amf_ue_id(amf_ue_id_t amf_ue_id_) { amf_ue_id = amf_ue_id_; } - -private: - ue_index_t ue_index = ue_index_t::invalid; - - amf_ue_id_t amf_ue_id = amf_ue_id_t::invalid; - ran_ue_id_t ran_ue_id = ran_ue_id_t::invalid; - uint64_t aggregate_maximum_bit_rate_dl = 0; - - ngap_rrc_ue_pdu_notifier* rrc_ue_pdu_notifier = nullptr; - ngap_rrc_ue_control_notifier* rrc_ue_ctrl_notifier = nullptr; - ngap_du_processor_control_notifier* du_processor_ctrl_notifier = nullptr; -}; - -struct dummy_ngap_ue_manager : public ngap_ue_manager { -public: - void set_ue_config(ue_configuration ue_config_) { ue_config = ue_config_; } - ue_configuration get_ue_config() override { return ue_config; } - - ue_index_t get_ue_index(pci_t pci, rnti_t c_rnti) override { return ue_index_t::invalid; } - - ngap_ue* add_ue(ue_index_t ue_index, - ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_, - ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier_, - ngap_du_processor_control_notifier& du_processor_ctrl_notifier_) override - { - ran_ue_id_t ran_ue_id = get_next_ran_ue_id(); - if (ran_ue_id == ran_ue_id_t::invalid) { - logger.error("No free RAN UE ID available"); - return nullptr; - } - - // Create UE object - ues.emplace(ue_index, ue_index); - auto& ue = ues.at(ue_index); - - ue.set_ran_ue_id(ran_ue_id); - ue.set_rrc_ue_pdu_notifier(rrc_ue_pdu_notifier_); - ue.set_rrc_ue_ctrl_notifier(rrc_ue_ctrl_notifier_); - ue.set_du_processor_ctrl_notifier(du_processor_ctrl_notifier_); - - // Add RAN UE ID to lookup - ran_ue_id_to_ue_index.emplace(ran_ue_id, ue_index); - - return &ue; - } - - void remove_ngap_ue(ue_index_t ue_index) override - { - // Remove UE from lookups - ran_ue_id_t ran_ue_id = ues.at(ue_index).get_ran_ue_id(); - if (ran_ue_id != ran_ue_id_t::invalid) { - ran_ue_id_to_ue_index.erase(ran_ue_id); - } - - amf_ue_id_t amf_ue_id = ues.at(ue_index).get_amf_ue_id(); - if (amf_ue_id != amf_ue_id_t::invalid) { - amf_ue_id_to_ue_index.erase(amf_ue_id); - } - - ues.erase(ue_index); - } - - ngap_ue* find_ngap_ue(ue_index_t ue_index) override - { - if (ues.find(ue_index) != ues.end()) { - return &ues.at(ue_index); - } - return nullptr; - } - - size_t get_nof_ngap_ues() override { return ues.size(); } - - ue_index_t get_ue_index(ran_ue_id_t ran_ue_id) override - { - if (ran_ue_id_to_ue_index.find(ran_ue_id) == ran_ue_id_to_ue_index.end()) { - logger.info("UE with ran_ue_id_t={} does not exist. Dropping PDU", ran_ue_id); - return ue_index_t::invalid; - } - return ran_ue_id_to_ue_index[ran_ue_id]; - } - - ue_index_t get_ue_index(amf_ue_id_t amf_ue_id) override - { - if (amf_ue_id_to_ue_index.find(amf_ue_id) == amf_ue_id_to_ue_index.end()) { - logger.info("UE with amf_ue_id_t={} does not exist. Dropping PDU", amf_ue_id); - return ue_index_t::invalid; - } - return amf_ue_id_to_ue_index[amf_ue_id]; - } - - void set_amf_ue_id(ue_index_t ue_index, amf_ue_id_t amf_ue_id) override - { - if (ue_index == ue_index_t::invalid) { - logger.error("Invalid ue_index={}", ue_index); - return; - } - - ues.at(ue_index).set_amf_ue_id(amf_ue_id); - // Add AMF UE ID to lookup - amf_ue_id_to_ue_index.emplace(amf_ue_id, ue_index); - } - - void transfer_ngap_ue_context(ue_index_t new_ue_index, ue_index_t old_ue_index) override - { - // Update ue index at lookups - ran_ue_id_to_ue_index.at(find_ran_ue_id(old_ue_index)) = new_ue_index; - amf_ue_id_to_ue_index.at(find_amf_ue_id(old_ue_index)) = new_ue_index; - - // transfer UE NGAP IDs to new UE - auto& old_ue = ues.at(old_ue_index); - auto& new_ue = ues.at(new_ue_index); - new_ue.set_ran_ue_id(old_ue.get_ran_ue_id()); - new_ue.set_amf_ue_id(old_ue.get_amf_ue_id()); - - // transfer aggregate maximum bit rate dl - new_ue.set_aggregate_maximum_bit_rate_dl(old_ue.get_aggregate_maximum_bit_rate_dl()); - - logger.debug( - "Transferred NGAP UE context from ueId={} (ran_ue_id={} amf_ue_id={}) to ueId={} (ran_ue_id={} amf_ue_id={})", - old_ue_index, - old_ue.get_ran_ue_id(), - old_ue.get_amf_ue_id(), - new_ue_index, - new_ue.get_ran_ue_id(), - new_ue.get_amf_ue_id()); - - // Remove old ue - ues.erase(old_ue_index); - } - -private: - ran_ue_id_t get_next_ran_ue_id() - { - // Search unallocated UE index - for (uint64_t i = 0; i < MAX_NOF_RAN_UES; i++) { - ran_ue_id_t next_ran_ue_id = uint_to_ran_ue_id(i); - if (ran_ue_id_to_ue_index.find(next_ran_ue_id) == ran_ue_id_to_ue_index.end()) { - return next_ran_ue_id; - } - } - - logger.error("No RAN UE ID available"); - return ran_ue_id_t::invalid; - } - - ran_ue_id_t find_ran_ue_id(ue_index_t ue_index) - { - unsigned ran_ue_id_uint = ran_ue_id_to_uint(ran_ue_id_t::min); - for (auto const& it : ran_ue_id_to_ue_index) { - if (it.second == ue_index) { - return uint_to_ran_ue_id(ran_ue_id_uint); - } - ran_ue_id_uint++; - } - logger.error("RAN UE ID for ue_index={} not found", ue_index); - return ran_ue_id_t::invalid; - } - - amf_ue_id_t find_amf_ue_id(ue_index_t ue_index) - { - unsigned amf_ue_id_uint = amf_ue_id_to_uint(amf_ue_id_t::min); - for (auto const& it : amf_ue_id_to_ue_index) { - if (it.second == ue_index) { - return uint_to_amf_ue_id(amf_ue_id_uint); - } - amf_ue_id_uint++; - } - logger.error("AMF UE ID for ue_index={} not found", ue_index); - return amf_ue_id_t::invalid; - } - - ue_configuration ue_config; - - std::unordered_map ues; // ues indexed by ue_index - std::unordered_map ran_ue_id_to_ue_index; // ue_indexes indexed by ran_ue_ids - std::unordered_map amf_ue_id_to_ue_index; // ue_indexes indexed by amf_ue_ids - - srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST"); + fifo_async_task_scheduler ctrl_loop{16}; + timer_manager& timer_db; + task_executor& exec; }; /// Reusable notifier class that a) stores the received msg for test inspection and b) @@ -344,17 +120,26 @@ class dummy_ngap_rrc_ue_notifier : public ngap_rrc_ue_pdu_notifier, public ngap_ logger.info("Received a NAS PDU"); } - async_task on_new_security_context(const asn1::ngap::ue_security_cap_s& caps, - const asn1::fixed_bitstring<256, false, true>& key) override + async_task on_new_security_context(const security::security_context& sec_context) override { logger.info("Received a new security context"); bool result = true; // NIA0 is not allowed - if (caps.nr_integrity_protection_algorithms.to_number() == 0) { - result = false; - } + security::preferred_integrity_algorithms inc_algo_pref_list = {security::integrity_algorithm::nia2, + security::integrity_algorithm::nia1, + security::integrity_algorithm::nia3, + security::integrity_algorithm::nia0}; + security::preferred_ciphering_algorithms ciph_algo_pref_list = {security::ciphering_algorithm::nea0, + security::ciphering_algorithm::nea2, + security::ciphering_algorithm::nea1, + security::ciphering_algorithm::nea3}; + + security::security_context tmp_ctxt; + tmp_ctxt = sec_context; + + result = tmp_ctxt.select_algorithms(inc_algo_pref_list, ciph_algo_pref_list); return launch_async([result](coro_context>& ctx) { CORO_BEGIN(ctx); @@ -364,7 +149,7 @@ class dummy_ngap_rrc_ue_notifier : public ngap_rrc_ue_pdu_notifier, public ngap_ bool on_security_enabled() override { return security_enabled; } - ngap_ue_source_handover_context on_ue_source_handover_context_required() override { return ho_context; } + byte_buffer on_handover_preparation_message_required() override { return ho_preparation_message.copy(); } bool on_new_rrc_handover_command(byte_buffer cmd) override { @@ -372,13 +157,16 @@ class dummy_ngap_rrc_ue_notifier : public ngap_rrc_ue_pdu_notifier, public ngap_ return true; } - void set_handover_context(ngap_ue_source_handover_context ho_context_) { ho_context = std::move(ho_context_); } + void set_ho_preparation_message(byte_buffer ho_preparation_message_) + { + ho_preparation_message = std::move(ho_preparation_message_); + } void set_security_enabled(bool enabled) { security_enabled = enabled; } - byte_buffer last_nas_pdu; - ngap_ue_source_handover_context ho_context; - bool security_enabled = true; - byte_buffer last_handover_command; + byte_buffer last_nas_pdu; + byte_buffer ho_preparation_message; + bool security_enabled = true; + byte_buffer last_handover_command; private: srslog::basic_logger& logger; @@ -388,7 +176,8 @@ class dummy_ngap_rrc_ue_notifier : public ngap_rrc_ue_pdu_notifier, public ngap_ class dummy_ngap_du_processor_notifier : public ngap_du_processor_control_notifier { public: - dummy_ngap_du_processor_notifier() : logger(srslog::fetch_basic_logger("TEST")){}; + dummy_ngap_du_processor_notifier(ngap_ue_context_removal_handler& ngap_handler_) : + logger(srslog::fetch_basic_logger("TEST")), ngap_handler(ngap_handler_){}; ue_index_t on_new_ue_index_required() override { @@ -462,6 +251,8 @@ class dummy_ngap_du_processor_notifier : public ngap_du_processor_control_notifi cu_cp_ue_context_release_complete release_complete; release_complete.ue_index = command.ue_index; + ngap_handler.remove_ue_context(command.ue_index); + return launch_async([release_complete](coro_context>& ctx) mutable { CORO_BEGIN(ctx); // TODO: Add values @@ -469,7 +260,7 @@ class dummy_ngap_du_processor_notifier : public ngap_du_processor_control_notifi }); } - rrc_ue_context_release_command last_command; + cu_cp_ue_context_release_command last_command; cu_cp_pdu_session_resource_setup_request last_request; cu_cp_pdu_session_resource_modify_request last_modify_request; cu_cp_pdu_session_resource_release_command last_release_command; @@ -489,8 +280,9 @@ class dummy_ngap_du_processor_notifier : public ngap_du_processor_control_notifi optional last_created_ue_index; private: - srslog::basic_logger& logger; - uint64_t ue_id = ue_index_to_uint(srs_cu_cp::ue_index_t::min); + srslog::basic_logger& logger; + uint64_t ue_id = ue_index_to_uint(srs_cu_cp::ue_index_t::min); + ngap_ue_context_removal_handler& ngap_handler; }; class dummy_ngap_cu_cp_paging_notifier : public ngap_cu_cp_du_repository_notifier diff --git a/tests/unittests/ofh/receiver/CMakeLists.txt b/tests/unittests/ofh/receiver/CMakeLists.txt index 48df3a18f1..66b84428c3 100644 --- a/tests/unittests/ofh/receiver/CMakeLists.txt +++ b/tests/unittests/ofh/receiver/CMakeLists.txt @@ -52,3 +52,7 @@ add_executable(ofh_uplane_rx_symbol_data_flow_writer_test ofh_uplane_rx_symbol_d target_link_libraries(ofh_uplane_rx_symbol_data_flow_writer_test srsran_ofh_receiver srsran_support gtest gtest_main) gtest_discover_tests(ofh_uplane_rx_symbol_data_flow_writer_test) +add_executable(ofh_sequence_id_checker_test ofh_sequence_id_checker_test.cpp) +target_link_libraries(ofh_sequence_id_checker_test srsran_support gtest gtest_main) +gtest_discover_tests(ofh_sequence_id_checker_test) + diff --git a/tests/unittests/ofh/receiver/ofh_data_flow_uplane_uplink_data_impl_test.cpp b/tests/unittests/ofh/receiver/ofh_data_flow_uplane_uplink_data_impl_test.cpp index 0f4dcae21c..1350819dfe 100644 --- a/tests/unittests/ofh/receiver/ofh_data_flow_uplane_uplink_data_impl_test.cpp +++ b/tests/unittests/ofh/receiver/ofh_data_flow_uplane_uplink_data_impl_test.cpp @@ -68,9 +68,9 @@ class data_flow_uplane_uplink_data_impl_fixture : public ::testing::Test uplane_rx_symbol_notifier_spy notifier; std::shared_ptr ul_cplane_context_repo_ptr = std::make_shared(1); - std::shared_ptr ul_context_repo = std::make_shared(1); - uplane_message_decoder_spy* uplane_decoder; - data_flow_uplane_uplink_data_impl data_flow; + std::shared_ptr ul_context_repo = std::make_shared(1); + uplane_message_decoder_spy* uplane_decoder; + data_flow_uplane_uplink_data_impl data_flow; public: data_flow_uplane_uplink_data_impl_fixture() : diff --git a/tests/unittests/ofh/receiver/ofh_message_receiver_test.cpp b/tests/unittests/ofh/receiver/ofh_message_receiver_test.cpp index 6b1e57b8e3..fb4c0d4253 100644 --- a/tests/unittests/ofh/receiver/ofh_message_receiver_test.cpp +++ b/tests/unittests/ofh/receiver/ofh_message_receiver_test.cpp @@ -117,7 +117,6 @@ class ofh_message_receiver_fixture : public ::testing::Test static_vector ul_eaxc = {4, 5}; rx_window_checker window_checker; ecpri::packet_parameters ecpri_params; - iq_decompressor_dummy decomp; data_flow_uplane_uplink_data_spy* df_uplink; data_flow_uplane_uplink_prach_spy* df_prach; ecpri_packet_decoder_spy* ecpri_decoder; @@ -134,35 +133,40 @@ class ofh_message_receiver_fixture : public ::testing::Test message_receiver_config generate_config() { message_receiver_config config; - config.vlan_params = vlan_params; - config.ul_eaxc = ul_eaxc; - config.ul_prach_eaxc = ul_prach_eaxc; + config.vlan_params = vlan_params; + config.ul_eaxc = ul_eaxc; + config.prach_eaxc = ul_prach_eaxc; return config; } message_receiver_dependencies generate_dependencies() { - message_receiver_dependencies depen; - depen.logger = &srslog::fetch_basic_logger("TEST"); - depen.window_checker = &window_checker; - depen.uplane_decoder = create_dynamic_compr_method_ofh_user_plane_packet_decoder( - *depen.logger, srsran::subcarrier_spacing::kHz30, cyclic_prefix::NORMAL, MAX_NOF_PRBS, decomp); + message_receiver_dependencies dependencies; + dependencies.logger = &srslog::fetch_basic_logger("TEST"); + dependencies.window_checker = &window_checker; + + dependencies.uplane_decoder = + create_dynamic_compr_method_ofh_user_plane_packet_decoder(*dependencies.logger, + srsran::subcarrier_spacing::kHz30, + cyclic_prefix::NORMAL, + MAX_NOF_PRBS, + std::make_unique()); { - auto temp = std::make_unique(); - df_prach = temp.get(); - depen.data_flow_prach = std::move(temp); + auto temp = std::make_unique(); + df_prach = temp.get(); + dependencies.data_flow_prach = std::move(temp); } { - auto temp = std::make_unique(); - df_uplink = temp.get(); - depen.data_flow_uplink = std::move(temp); + auto temp = std::make_unique(); + df_uplink = temp.get(); + dependencies.data_flow_uplink = std::move(temp); } { - auto temp = std::make_unique(vlan_params); - vlan_decoder = temp.get(); - depen.eth_frame_decoder = std::move(temp); + auto temp = std::make_unique(vlan_params); + vlan_decoder = temp.get(); + dependencies.eth_frame_decoder = std::move(temp); } { ecpri_params.header.msg_type = ecpri::message_type::iq_data; @@ -171,11 +175,11 @@ class ofh_message_receiver_fixture : public ::testing::Test ecpri_params.header.revision = 1U; ecpri_params.type_params.emplace(ecpri::iq_data_parameters{1, 2}); - auto temp = std::make_unique(ecpri_params); - ecpri_decoder = temp.get(); - depen.ecpri_decoder = std::move(temp); + auto temp = std::make_unique(ecpri_params); + ecpri_decoder = temp.get(); + dependencies.ecpri_decoder = std::move(temp); } - return depen; + return dependencies; } }; diff --git a/tests/unittests/ofh/receiver/ofh_sequence_id_checker_test.cpp b/tests/unittests/ofh/receiver/ofh_sequence_id_checker_test.cpp new file mode 100644 index 0000000000..8d6eb3a4d2 --- /dev/null +++ b/tests/unittests/ofh/receiver/ofh_sequence_id_checker_test.cpp @@ -0,0 +1,182 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "../../../../lib/ofh/receiver/ofh_sequence_id_checker.h" +#include + +using namespace srsran; +using namespace ofh; + +TEST(ofh_sequence_id_checker, first_message_is_always_ok) +{ + sequence_id_checker checker; + unsigned eaxc = 0; + unsigned seq_id = 1; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); +} + +TEST(ofh_sequence_id_checker, consecutive_messages_is_ok) +{ + sequence_id_checker checker; + unsigned eaxc = 0; + unsigned seq_id = 1; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); + + ++seq_id; + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); + + ++seq_id; + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); +} + +TEST(ofh_sequence_id_checker, message_from_the_past_is_detected) +{ + sequence_id_checker checker; + unsigned eaxc = 0; + unsigned seq_id = 1; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); + + --seq_id; + + ASSERT_EQ(-2, checker.update_and_compare_seq_id(eaxc, seq_id)); +} + +TEST(ofh_sequence_id_checker, message_from_the_past_is_detected_with_big_difference) +{ + sequence_id_checker checker; + unsigned eaxc = 0; + unsigned seq_id = 14; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); + + seq_id = 235; + + ASSERT_EQ(-36, checker.update_and_compare_seq_id(eaxc, seq_id)); +} + +TEST(ofh_sequence_id_checker, message_from_the_past_in_the_edge_is_detected) +{ + sequence_id_checker checker; + unsigned eaxc = 0; + uint8_t seq_id = 255; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); + ASSERT_EQ(-1, checker.update_and_compare_seq_id(eaxc, seq_id)); +} + +TEST(ofh_sequence_id_checker, message_from_the_future_is_detected) +{ + sequence_id_checker checker; + unsigned eaxc = 0; + unsigned seq_id = 1; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); + + seq_id += 4; + + ASSERT_EQ(3, checker.update_and_compare_seq_id(eaxc, seq_id)); +} + +TEST(ofh_sequence_id_checker, message_from_the_future_in_the_edge_is_detected) +{ + sequence_id_checker checker; + unsigned eaxc = 0; + unsigned seq_id = 254; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); + + seq_id = 0; + + ASSERT_EQ(1, checker.update_and_compare_seq_id(eaxc, seq_id)); +} + +TEST(ofh_sequence_id_checker, message_from_the_future_is_detected_with_big_difference) +{ + sequence_id_checker checker; + unsigned eaxc = 0; + unsigned seq_id = 234; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc, seq_id)); + + seq_id = 30; + + ASSERT_EQ(51, checker.update_and_compare_seq_id(eaxc, seq_id)); +} + +TEST(ofh_sequence_id_checker, two_eaxc_with_different_seq_id_is_ok) +{ + sequence_id_checker checker; + unsigned eaxc_1 = 0; + unsigned seq_id_1 = 1; + unsigned eaxc_2 = 2; + unsigned seq_id_2 = 200; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc_1, seq_id_1)); + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc_2, seq_id_2)); + + for (unsigned i = 0; i != 10; ++i) { + ++seq_id_1; + ++seq_id_2; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc_1, seq_id_1)); + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc_2, seq_id_2)); + } +} + +TEST(ofh_sequence_id_checker, two_eaxc_with_different_seq_id_detects_past_message) +{ + sequence_id_checker checker; + unsigned eaxc_1 = 0; + unsigned seq_id_1 = 1; + unsigned eaxc_2 = 2; + unsigned seq_id_2 = 200; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc_1, seq_id_1)); + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc_2, seq_id_2)); + + seq_id_1 = 250; + seq_id_2 = 100; + + ASSERT_EQ(-8, checker.update_and_compare_seq_id(eaxc_1, seq_id_1)); + ASSERT_EQ(-101, checker.update_and_compare_seq_id(eaxc_2, seq_id_2)); +} + +TEST(ofh_sequence_id_checker, two_eaxc_with_different_seq_id_detects_future_message) +{ + sequence_id_checker checker; + unsigned eaxc_1 = 0; + unsigned seq_id_1 = 1; + unsigned eaxc_2 = 2; + unsigned seq_id_2 = 200; + + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc_1, seq_id_1)); + ASSERT_EQ(0, checker.update_and_compare_seq_id(eaxc_2, seq_id_2)); + + seq_id_1 = 105; + seq_id_2 = 15; + + ASSERT_EQ(103, checker.update_and_compare_seq_id(eaxc_1, seq_id_1)); + ASSERT_EQ(70, checker.update_and_compare_seq_id(eaxc_2, seq_id_2)); +} diff --git a/tests/unittests/ofh/receiver/ofh_uplane_rx_symbol_data_flow_notifier_test.cpp b/tests/unittests/ofh/receiver/ofh_uplane_rx_symbol_data_flow_notifier_test.cpp index ea80bf104a..b828fcfd81 100644 --- a/tests/unittests/ofh/receiver/ofh_uplane_rx_symbol_data_flow_notifier_test.cpp +++ b/tests/unittests/ofh/receiver/ofh_uplane_rx_symbol_data_flow_notifier_test.cpp @@ -32,7 +32,7 @@ using namespace ofh::testing; TEST(ofh_data_flow_uplane_rx_symbol_notifier, empty_context_does_not_notify) { uplane_rx_symbol_notifier_spy notifier; - auto repo = std::make_shared(1); + auto repo = std::make_shared(1); uplane_rx_symbol_data_flow_notifier sender(srslog::fetch_basic_logger("TEST"), repo, notifier); slot_point slot(0, 0, 1); unsigned symbol = 0; @@ -47,7 +47,7 @@ TEST(ofh_data_flow_uplane_rx_symbol_notifier, empty_context_does_not_notify) TEST(ofh_data_flow_uplane_rx_symbol_notifier, unwritten_grid_does_not_notify) { uplane_rx_symbol_notifier_spy notifier; - auto repo = std::make_shared(1); + auto repo = std::make_shared(1); uplane_rx_symbol_data_flow_notifier sender(srslog::fetch_basic_logger("TEST"), repo, notifier); slot_point slot(0, 0, 1); unsigned symbol = 0; @@ -65,7 +65,7 @@ TEST(ofh_data_flow_uplane_rx_symbol_notifier, unwritten_grid_does_not_notify) TEST(ofh_data_flow_uplane_rx_symbol_notifier, completed_resource_grid_triggers_notification) { uplane_rx_symbol_notifier_spy notifier; - auto repo = std::make_shared(1); + auto repo = std::make_shared(1); uplane_rx_symbol_data_flow_notifier sender(srslog::fetch_basic_logger("TEST"), repo, notifier); slot_point slot(0, 0, 1); unsigned symbol = 0; @@ -94,7 +94,7 @@ TEST(ofh_data_flow_uplane_rx_symbol_notifier, completed_resource_grid_triggers_n TEST(ofh_data_flow_uplane_rx_symbol_notifier, uncompleted_port_does_not_notify) { uplane_rx_symbol_notifier_spy notifier; - auto repo = std::make_shared(1); + auto repo = std::make_shared(1); uplane_rx_symbol_data_flow_notifier sender(srslog::fetch_basic_logger("TEST"), repo, notifier); slot_point slot(0, 0, 1); unsigned symbol = 0; @@ -117,7 +117,7 @@ TEST(ofh_data_flow_uplane_rx_symbol_notifier, uncompleted_port_does_not_notify) TEST(ofh_data_flow_uplane_rx_symbol_notifier, uncompleted_prbs_does_not_notify) { uplane_rx_symbol_notifier_spy notifier; - auto repo = std::make_shared(1); + auto repo = std::make_shared(1); uplane_rx_symbol_data_flow_notifier sender(srslog::fetch_basic_logger("TEST"), repo, notifier); slot_point slot(0, 0, 1); unsigned symbol = 0; diff --git a/tests/unittests/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer_test.cpp b/tests/unittests/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer_test.cpp index 2c282b9061..dd9f9cb94a 100644 --- a/tests/unittests/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer_test.cpp +++ b/tests/unittests/ofh/receiver/ofh_uplane_rx_symbol_data_flow_writer_test.cpp @@ -33,7 +33,7 @@ class ofh_uplane_rx_symbol_data_flow_writer_fixture : public ::testing::Test { protected: static_vector eaxc = {0, 1, 2, 3}; - std::shared_ptr repo = std::make_shared(1); + std::shared_ptr repo = std::make_shared(1); unsigned sector = 0; slot_point slot; unsigned symbol_id = 0; diff --git a/tests/unittests/ofh/serdes/ofh_uplane_packet_decoder_dynamic_impl_test.cpp b/tests/unittests/ofh/serdes/ofh_uplane_packet_decoder_dynamic_impl_test.cpp index c5d5250112..2de36ec0bb 100644 --- a/tests/unittests/ofh/serdes/ofh_uplane_packet_decoder_dynamic_impl_test.cpp +++ b/tests/unittests/ofh/serdes/ofh_uplane_packet_decoder_dynamic_impl_test.cpp @@ -56,13 +56,12 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, valid_packet_should_decode_correctl 0x02, 0x9e, 0x02, 0x9e, 0x02, 0xa8, 0x02, 0xa8, 0x02, 0xb2, 0x02, 0xb2, 0x02, 0xbc, 0x02, 0xbc, 0x02, 0xc6, 0x02, 0xc6, 0x02, 0xd0, 0x02, 0xd0, 0x02, 0xda, 0x02, 0xda}; - iq_decompressor_dummy dummy_decomp; const ru_compression_params comp_params = {compression_type::none, 16}; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -96,12 +95,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, missing_one_iq_sample_must_fail) 0x02, 0x80, 0x02, 0x80, 0x02, 0x8a, 0x02, 0x8a, 0x02, 0x94, 0x02, 0x94, 0x02, 0x9e, 0x02, 0x9e, 0x02, 0xa8, 0x02, 0xa8, 0x02, 0xb2, 0x02, 0xb2, 0x02, 0xbc, 0x02, 0xbc, 0x02, 0xc6, 0x02, 0xc6, 0x02, 0xd0, 0x02, 0xd0}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -120,12 +118,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, missing_one_prb_must_fail) 0x02, 0x12, 0x02, 0x1c, 0x02, 0x1c, 0x02, 0x26, 0x02, 0x26, 0x02, 0x30, 0x02, 0x30, 0x02, 0x3a, 0x02, 0x3a, 0x02, 0x44, 0x02, 0x44, 0x02, 0x4e, 0x02, 0x4e, 0x02, 0x58, 0x02, 0x58, 0x02}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -144,12 +141,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, dynamic_compression_with_no_compres 0x02, 0x12, 0x02, 0x12, 0x02, 0x1c, 0x02, 0x1c, 0x02, 0x26, 0x02, 0x26, 0x02, 0x30, 0x02, 0x30, 0x02, 0x3a, 0x02, 0x3a, 0x02, 0x44, 0x02, 0x44, 0x02, 0x4e, 0x02, 0x4e, 0x02, 0x58, 0x02, 0x58, 0x02}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -166,12 +162,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, decoding_one_section_and_failing_to 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01, 0xea, 0x01, 0xea, 0x01, 0xea, 0x01, 0xea}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -184,12 +179,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, missing_section_header_must_fail) { std::vector packet = {0x10, 0x02, 0x40, 0x42, 0x00, 0x70, 0x24}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -201,12 +195,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, missing_header_must_fail) { std::vector packet = {0x10, 0x02, 0x40}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -218,12 +211,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, missing_compression_header_must_fai { std::vector packet = {0x10, 0x02, 0x40, 0x42, 0x00, 0x70, 0x24, 0x01, 0x00}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -238,12 +230,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, downlink_packet_should_fail) 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01, 0xea, 0x01, 0xea}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -258,12 +249,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, reserved_filter_index_should_fail) 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01, 0xea, 0x01, 0xea}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -278,12 +268,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, symbol_index_out_of_range_should_fa 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01, 0xea, 0x01, 0xea}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -298,13 +287,12 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, none_compression_with_15_bits_shoul 0x01, 0xa4, 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01}; - iq_decompressor_dummy dummy_decomp; const ru_compression_params comp_params = {compression_type::none, 15}; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -322,13 +310,12 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, bfp_with_9_bits_should_pass) 0xa4, 0x01, 0xa4, 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01}; - iq_decompressor_dummy dummy_decomp; const ru_compression_params comp_params = {compression_type::BFP, 9}; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -346,12 +333,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, bfp_with_15_bits_without_ud_comp_pa 0x01, 0xa4, 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -369,12 +355,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, if_message_num_prbs_equals_zero_dec const unsigned ru_nof_prbs = 2; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), ru_nof_prbs, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); @@ -393,12 +378,11 @@ TEST(ofh_uplane_packet_decoder_dynamic_impl, if_message_contains_one_valid_secti const unsigned ru_nof_prbs = 2; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_dynamic_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), ru_nof_prbs, - dummy_decomp); + std::make_unique()); uplane_message_decoder_results results; bool decode_result = decoder.decode(results, packet); diff --git a/tests/unittests/ofh/serdes/ofh_uplane_packet_decoder_static_impl_test.cpp b/tests/unittests/ofh/serdes/ofh_uplane_packet_decoder_static_impl_test.cpp index ed978740f7..dd186ec3d5 100644 --- a/tests/unittests/ofh/serdes/ofh_uplane_packet_decoder_static_impl_test.cpp +++ b/tests/unittests/ofh/serdes/ofh_uplane_packet_decoder_static_impl_test.cpp @@ -55,12 +55,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, valid_packet_should_decode_correctly 0x02, 0x80, 0x02, 0x8a, 0x02, 0x8a, 0x02, 0x94, 0x02, 0x94, 0x02, 0x9e, 0x02, 0x9e, 0x02, 0xa8, 0x02, 0xa8, 0x02, 0xb2, 0x02, 0xb2, 0x02, 0xbc, 0x02, 0xbc, 0x02, 0xc6, 0x02, 0xc6, 0x02, 0xd0, 0x02, 0xd0, 0x02, 0xda, 0x02, 0xda}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -94,12 +93,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, missing_one_iq_sample_must_fail) 0x02, 0x80, 0x02, 0x8a, 0x02, 0x8a, 0x02, 0x94, 0x02, 0x94, 0x02, 0x9e, 0x02, 0x9e, 0x02, 0xa8, 0x02, 0xa8, 0x02, 0xb2, 0x02, 0xb2, 0x02, 0xbc, 0x02, 0xbc, 0x02, 0xc6, 0x02, 0xc6, 0x02, 0xd0, 0x02, 0xd0}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -119,12 +117,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, missing_one_prb_must_fail) 0x02, 0x12, 0x02, 0x12, 0x02, 0x1c, 0x02, 0x1c, 0x02, 0x26, 0x02, 0x26, 0x02, 0x30, 0x02, 0x30, 0x02, 0x3a, 0x02, 0x3a, 0x02, 0x44, 0x02, 0x44, 0x02, 0x4e, 0x02, 0x4e, 0x02, 0x58, 0x02, 0x58, 0x02}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -144,12 +141,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, static_compression_with_compression_ 0x02, 0x12, 0x02, 0x1c, 0x02, 0x1c, 0x02, 0x26, 0x02, 0x26, 0x02, 0x30, 0x02, 0x30, 0x02, 0x3a, 0x02, 0x3a, 0x02, 0x44, 0x02, 0x44, 0x02, 0x4e, 0x02, 0x4e, 0x02, 0x58, 0x02, 0x58, 0x02}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -167,12 +163,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, decoding_one_section_and_failing_to_ 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01, 0xea, 0x01, 0xea, 0x01, 0xea, 0x01, 0xea}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -187,12 +182,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, missing_section_header_must_fail) { std::vector packet = {0x10, 0x02, 0x40, 0x42, 0x00, 0x70, 0x24}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -206,12 +200,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, missing_header_must_fail) { std::vector packet = {0x10, 0x02, 0x40}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -228,12 +221,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, downlink_packet_should_fail) 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01, 0xea, 0x01, 0xea}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -250,12 +242,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, reserved_filter_index_should_fail) 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01, 0xea, 0x01, 0xea}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -272,12 +263,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, symbol_index_out_of_range_should_fai 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01, 0xea, 0x01, 0xea}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 16}, {compression_type::none, 16}); @@ -294,12 +284,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, none_compression_with_15_bits_should 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::none, 15}, {compression_type::none, 15}); @@ -316,12 +305,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, bfp_with_15_bits_should_pass) 0xa4, 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::BFP, 15}, {compression_type::BFP, 15}); @@ -338,12 +326,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, bfp_with_15_bits_without_ud_comp_len 0x01, 0xae, 0x01, 0xae, 0x01, 0xb8, 0x01, 0xb8, 0x01, 0xc2, 0x01, 0xc2, 0x01, 0xcc, 0x01, 0xcc, 0x01, 0xd6, 0x01, 0xd6, 0x01, 0xe0, 0x01, 0xe0, 0x01}; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), 273, - dummy_decomp, + std::make_unique(), {compression_type::BFP, 15}, {compression_type::BFP, 15}); @@ -363,12 +350,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, if_message_num_prbs_equals_zero_deco const unsigned ru_nof_prbs = 2; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), ru_nof_prbs, - dummy_decomp, + std::make_unique(), {compression_type::BFP, 9}, {compression_type::BFP, 9}); @@ -389,12 +375,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, if_message_contains_one_valid_sectio const unsigned ru_nof_prbs = 2; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), ru_nof_prbs, - dummy_decomp, + std::make_unique(), {compression_type::BFP, 9}, {compression_type::BFP, 9}); @@ -416,12 +401,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, peek_filter_index) const unsigned ru_nof_prbs = 2; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), ru_nof_prbs, - dummy_decomp, + std::make_unique(), {compression_type::BFP, 9}, {compression_type::BFP, 9}); @@ -431,15 +415,13 @@ TEST(ofh_uplane_packet_decoder_static_impl, peek_filter_index) TEST(ofh_uplane_packet_decoder_static_impl, peek_filter_index_returns_reserved_on_peek_failure) { std::vector packet; + const unsigned ru_nof_prbs = 2; - const unsigned ru_nof_prbs = 2; - - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), ru_nof_prbs, - dummy_decomp, + std::make_unique(), {compression_type::BFP, 9}, {compression_type::BFP, 9}); @@ -456,12 +438,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, peek_prach_filter_index) const unsigned ru_nof_prbs = 2; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), ru_nof_prbs, - dummy_decomp, + std::make_unique(), {compression_type::BFP, 9}, {compression_type::BFP, 9}); @@ -480,12 +461,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, peek_slot_symbol_point) const unsigned ru_nof_prbs = 2; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), ru_nof_prbs, - dummy_decomp, + std::make_unique(), {compression_type::BFP, 9}, {compression_type::BFP, 9}); @@ -498,12 +478,11 @@ TEST(ofh_uplane_packet_decoder_static_impl, return_invalid_slot_point_on_packet_ const unsigned ru_nof_prbs = 2; - iq_decompressor_dummy dummy_decomp; uplane_message_decoder_static_compression_impl decoder(srslog::fetch_basic_logger("TEST"), subcarrier_spacing::kHz30, get_nsymb_per_slot(cyclic_prefix::NORMAL), ru_nof_prbs, - dummy_decomp, + std::make_unique(), {compression_type::BFP, 9}, {compression_type::BFP, 9}); diff --git a/tests/unittests/ofh/transmitter/ofh_uplink_request_handler_impl_test.cpp b/tests/unittests/ofh/transmitter/ofh_uplink_request_handler_impl_test.cpp index ec1a1ca1ce..f37600fb18 100644 --- a/tests/unittests/ofh/transmitter/ofh_uplink_request_handler_impl_test.cpp +++ b/tests/unittests/ofh/transmitter/ofh_uplink_request_handler_impl_test.cpp @@ -175,16 +175,17 @@ class resource_grid_dummy : public resource_grid class ofh_uplink_request_handler_impl_fixture : public ::testing::Test { protected: - std::shared_ptr> ul_slot_repo; - std::shared_ptr> ul_prach_repo; - data_flow_cplane_scheduling_commands_spy* data_flow; - data_flow_cplane_scheduling_commands_spy* data_flow_prach; - uplink_request_handler_impl handler; - uplink_request_handler_impl handler_prach_cp_en; + uplink_request_handler_impl_config cfg; + std::shared_ptr ul_slot_repo; + std::shared_ptr ul_prach_repo; + data_flow_cplane_scheduling_commands_spy* data_flow; + data_flow_cplane_scheduling_commands_spy* data_flow_prach; + uplink_request_handler_impl handler; + uplink_request_handler_impl handler_prach_cp_en; explicit ofh_uplink_request_handler_impl_fixture() : - ul_slot_repo(std::make_shared>(REPOSITORY_SIZE)), - ul_prach_repo(std::make_shared>(REPOSITORY_SIZE)), + ul_slot_repo(std::make_shared(REPOSITORY_SIZE)), + ul_prach_repo(std::make_shared(REPOSITORY_SIZE)), handler(get_config_prach_cp_disabled(), get_dependencies_prach_cp_disabled()), handler_prach_cp_en(get_config_prach_cp_enabled(), get_dependencies_prach_cp_enabled()) { @@ -296,10 +297,4 @@ TEST_F(ofh_uplink_request_handler_impl_fixture, handle_uplink_slot_generates_cpl ASSERT_EQ(rg_context.slot, info.slot); ASSERT_EQ(eaxc[0], info.eaxc); ASSERT_EQ(data_direction::uplink, info.direction); - - // Assert repository. - ul_slot_context slot_ctx = ul_slot_repo->get(rg_context.slot); - uplane_rx_symbol_notifier_spy notif_spy; - slot_ctx.notify_symbol(0, notif_spy); - ASSERT_EQ(notif_spy.get_reasource_grid_reader(), &rg.get_reader()); } diff --git a/tests/unittests/pcap/mac_pcap_test.cpp b/tests/unittests/pcap/mac_pcap_test.cpp index d5bcdaa426..0d2f825a74 100644 --- a/tests/unittests/pcap/mac_pcap_test.cpp +++ b/tests/unittests/pcap/mac_pcap_test.cpp @@ -25,6 +25,7 @@ #include void write_pcap_nr_thread_function_byte_buffer(srsran::mac_pcap* pcap, uint32_t num_pdus); +void write_pcap_nr_thread_function_large_byte_buffer(srsran::mac_pcap* pcap, uint32_t num_pdus); void write_pcap_nr_thread_function_spans(srsran::mac_pcap* pcap, uint32_t num_pdus); class pcap_mac_test : public ::testing::Test @@ -56,7 +57,7 @@ class pcap_mac_test : public ::testing::Test TEST_F(pcap_mac_test, write_pdu) { - mac_pcap_writer.open("mac_write_pdu.pcap"); + mac_pcap_writer.open("mac_write_pdu.pcap", srsran::mac_pcap_type::udp); std::array tv = { 0x04, 0x0a, 0x0d, 0x72, 0x80, 0xd3, 0x96, 0x02, 0x7b, 0x01, 0xbd, 0x26, 0x3f, 0x00, 0x00, 0x00, 0x00}; int crnti = 0x01011; @@ -77,7 +78,7 @@ TEST_F(pcap_mac_test, write_pdu) TEST_F(pcap_mac_test, write_many_spans) { - mac_pcap_writer.open("mac_write_many_spans.pcap"); + mac_pcap_writer.open("mac_write_many_spans.pcap", srsran::mac_pcap_type::udp); uint32_t num_threads = 10; uint32_t num_pdus_per_thread = 100; @@ -98,7 +99,7 @@ TEST_F(pcap_mac_test, write_many_spans) TEST_F(pcap_mac_test, write_many_byte_buffers) { - mac_pcap_writer.open("mac_write_many_byte_buffers.pcap"); + mac_pcap_writer.open("mac_write_many_byte_buffers.pcap", srsran::mac_pcap_type::udp); uint32_t num_threads = 10; uint32_t num_pdus_per_thread = 100; @@ -118,6 +119,92 @@ TEST_F(pcap_mac_test, write_many_byte_buffers) } } +TEST_F(pcap_mac_test, write_dlt_pdu) +{ + mac_pcap_writer.open("mac_write_dlt_pdu.pcap", srsran::mac_pcap_type::dlt); + std::array tv = { + 0x04, 0x0a, 0x0d, 0x72, 0x80, 0xd3, 0x96, 0x02, 0x7b, 0x01, 0xbd, 0x26, 0x3f, 0x00, 0x00, 0x00, 0x00}; + int crnti = 0x01011; + int ue_id = 2; + int harqid = 0; + int tti = 10; + srsran::mac_nr_context_info context = {}; + context.radioType = srsran::PCAP_FDD_RADIO; + context.direction = srsran::PCAP_DIRECTION_DOWNLINK; + context.rntiType = srsran::PCAP_C_RNTI; + context.rnti = crnti; + context.ueid = ue_id; + context.harqid = harqid; + context.system_frame_number = tti / 10; + context.sub_frame_number = tti % 10; + mac_pcap_writer.push_pdu(context, tv); +} + +TEST_F(pcap_mac_test, write_many_dlt_spans) +{ + mac_pcap_writer.open("mac_write_many_dlt_spans.pcap", srsran::mac_pcap_type::dlt); + + uint32_t num_threads = 10; + uint32_t num_pdus_per_thread = 100; + + std::vector writer_threads; + test_logger.info("Start writer_threads"); + + for (uint32_t i = 0; i < num_threads; i++) { + writer_threads.push_back(std::thread(write_pcap_nr_thread_function_spans, &mac_pcap_writer, num_pdus_per_thread)); + } + + test_logger.info("Wait for writer_threads to finish"); + // wait for threads to finish + for (std::thread& thread : writer_threads) { + thread.join(); + } +} + +TEST_F(pcap_mac_test, write_many_dlt_byte_buffers) +{ + mac_pcap_writer.open("mac_write_many_dlt_spans.pcap", srsran::mac_pcap_type::dlt); + + uint32_t num_threads = 10; + uint32_t num_pdus_per_thread = 100; + + std::vector writer_threads; + test_logger.info("Start writer_threads"); + + for (uint32_t i = 0; i < num_threads; i++) { + writer_threads.push_back( + std::thread(write_pcap_nr_thread_function_byte_buffer, &mac_pcap_writer, num_pdus_per_thread)); + } + + test_logger.info("Wait for writer_threads to finish"); + // wait for threads to finish + for (std::thread& thread : writer_threads) { + thread.join(); + } +} + +TEST_F(pcap_mac_test, write_large_byte_buffers) +{ + mac_pcap_writer.open("mac_write_many_dlt_large_buffers.pcap", srsran::mac_pcap_type::dlt); + + uint32_t num_threads = 10; + uint32_t num_pdus_per_thread = 2; + + std::vector writer_threads; + test_logger.info("Start writer_threads"); + + for (uint32_t i = 0; i < num_threads; i++) { + writer_threads.push_back( + std::thread(write_pcap_nr_thread_function_large_byte_buffer, &mac_pcap_writer, num_pdus_per_thread)); + } + + test_logger.info("Wait for writer_threads to finish"); + // wait for threads to finish + for (std::thread& thread : writer_threads) { + thread.join(); + } +} + // Write #num_pdus DL MAC NR PDUs using PCAP handle (spans) void write_pcap_nr_thread_function_spans(srsran::mac_pcap* pcap, uint32_t num_pdus) { @@ -167,3 +254,1458 @@ void write_pcap_nr_thread_function_byte_buffer(srsran::mac_pcap* pcap, uint32_t std::cout << "Finished thread " << std::this_thread::get_id() << "\n"; } + +// Write #num_pdus DL MAC NR PDUs using PCAP handle (byte_buffer) +void write_pcap_nr_thread_function_large_byte_buffer(srsran::mac_pcap* pcap, uint32_t num_pdus) +{ + srsran::byte_buffer tv = srsran::make_byte_buffer( + + "44057e80042080042045000578b506400040066c1c0a2d0001" + "0a2d0003145196440dc78535f4439577801001fd348500000101080a7e069e553740bf250a43116ae9c7fb384dde95580b51f982cee5b1d7" + "2ffb703548c3f9a9220c5434ff30c1d04affa45a62c111fa45e4099dfec3dd946df21545009889b77dcda48b80de4903c13b8844a6dcb31e" + "0a2b6992d01f76f64cd621ef993f2f09cc0fa9a825ee8c9a92e9fff812924111ddda77b58b3e288050e4a9d3372d448308f6a684d4b1ef22" + "217d33e79aeadbdbf3898c2c1cac791fd568013eea27e58dd4ebe01e5a66ec6189a3836148125b58b8428542cbe45aaa0541bddcfee70041" + "53ac8da5a485ea225226839f168a0b256f91d00a011afee18a23596903ba8234e100d97b99f32b6c8206e860b0ff4d294ff73d9e67ba6498" + "385b8ac567ff3e18d8a2a2a79eb3668c1572d5a62cd7a5e46ef6467141dfd7cb5ef0f7307f752f4ff0cc57a2a8c6e8d0792bc9aad7bb2b25" + "4902613899ab35977f751437a0d583584bc7f37ecdb5c1281a2a62e6dd4a8d7383144f143756f2d0c96945526c54734065f54a091290324f" + "0acf452f3d8a8820753f4886c342eefe159738052c2de003822c3473b0bf17cd0953b1a9b37a1bd13b311cf7bdb295cb6978554e32f42222" + "ae7fec12c4d28775ef2cb53d7b99e33d344fabf01ee23458bf63555b4bf709c05a3f24171a8c4ec8c370eca990fb954ab9ce4d86445c7e24" + "16c61a8d5a3bc2222c22e31a08bea9b7d6505ce242d5f7c2340095ed0758971080c235807cac4e78eeaf343a9f24e6a1458e15b26ed72d2c" + "1dda1eb977ea197ed474013cdcf3315d3ab51d783a6c46f5b412a69d285c79718b66a4b655a36b3509420c4ef3ed49a2895123fc6613e224" + "d6a11663d8bc40b8f831b919b901d18b518ef2fa3ad41a0d7f9b11a2ddc388a612969816f7c980408e8a64e083cb3fdcae6f1d3e1aa53ef1" + "19de5c81d11ad9eff93312a8cd685773a794e9755e2b339f57d483df2f9022a8be8d2b883df028506e411c4e422b2b80330169d68927c0e3" + "d9202a88ed1ad0cc561e00875e6829263d1386cb6cdf0f5ac6b13f158b012a59d06931c82148b8eed4b6159bf7f68a640daaf15a94fa4032" + "0896fdc31af5e1204138ead463ed2d2ca5dc2f1e3c5e64f8a56f7de2b86f8ac075b89aff42c9544e9e2c064febb3d4c56ed1519fdaba9669" + "993fc9387e1e21146ee0da4a5cde5be28ed92bd6391925ce90deda54d79e7ab52e125081659f74af8e06c1217b32e84222c5081f20e69c82" + "87d09a2bd3c84724e14668021bf59ac4e6e8ba7a28c089b4f5e65f8f86c63592d97bc4d16549e034e44d94bc5f2f4866da35cded24ef3b92" + "57a8b8068eca7fb9f032430833141996a7d9a17bfebe05b6850bf4c044c0373e96d8b72088e93801817d988d5db23ed3fef99e35c680894c" + "6cae023ebaa14197ca6da0c4735fb12da676a714077afb155aeb4adcaece2df5d5e30b54dc302b51d3b7b54de5eb496f1bae6681b7457c0a" + "05bb64c4799faf3d3d44486027068a1242e6627bf14595b6f7f02d2d2991cc0d97667f8dbf2394703c9de80ab67adc11e3af7172be9575c2" + "ba5fb7d0e260b75094fb0e051a8303b92cba15c54300bdbadf46408bc3517d0d1fdc626f68b237c5d8e6590411d2f91674ed26cf0ae9f736" + "66129e0f0ef79fc949a28c3dc1f4f19e311ccebfc1558c6f8e8ae57711e48a1288ef06b1a02bfa36a56682770e93c30255391656ba5aa449" + "df16b614c28cb162f2d4d42aef7b6bca4fb3695a0691e7d90e94c4c10ca7ba6cc6de567ecc5f73aa3386668a32aaea932abbf6778adf0cf0" + "59754a012d1f9ceffaa982e3a6b7bd5abac19cef27745418b902a866d29b3af0334c7b57cc30e5dd89f0ea9bb1e02a73efe4ab2e32a4dc7d" + "c6b551cc803f0ed04b8e810bee82041fa3f6768a2119c0646d10ccbc83c84099284693332ac2882f44057e80042180042145000578b50740" + "0040066c1b0a2d00010a2d0003145196440dc78a79f4439577801001fdb48a00000101080a7e069e553740bf25484806cbf464708f9f23a2" + "a08c13a3342490ffe0a6aad4973f63303e44f852669a2ea5c07abecfea49111c184281a096edf756aa96f7d87fc0f43d8292e8aea230d342" + "15dc876bb4ae46b44e537ea28f62b4705c9f28f6c0c310fdeb9945051583f32eda754cc01106668b054cd4c5d408f711d440458d526885a9" + "7541b254ca03a4b486cab92c1509309c0c80d5dafbedb5b541067157c3064059e4f7724278162ae3f9e1372c958e4588dcbdcc8dbac98087" + "0f54e2a04f350ed73d0c8b11f41f77419f08e5c8baaaa68402f9d88390a15ea2f402d527237fab9ccec2263efa27a5d3d476b102e18e1737" + "7fc6059cc3fc0b81841de591f6c0c7bef38bc14656b9ffe8c68037bf9e11b8c9263fd6f03c1adb4b4a41fe07474b48a72ab1b3c801e9e2fc" + "5664fa17e08a6af66e358ea128d6c4829fe8ede92f4b9d1f376f94ee0d13a4a937acf7e64426acdfe17df5165b3d5dc92b572fcafce26f0d" + "7ea4f411838fb072fe86a9c612463f144c51cb526acb852fc402ad018e0e8cca61e39197957190ea34822c052357ec09ccdd6a622c8299cd" + "cc609188a66d141a0dd77af8985cbb960098e976fe85d526a19455f79c4d52b591594b21fe7aba09021f46e31d2d1b709e101223ff75b9aa" + "17e07880f07152a9f6cca18501221eb6c1b31fae6ccb95260652cc2a74abc590f141608148419a966fc4f77f402f07f5b39a3844e64aafbb" + "7ada9c37f9cca718547618ff38d6472d06e4d3838e7ed06bae797cbe62be020d66a8b92efc17eafe9e4c04846d9aaaf304c54622559a3102" + "7a3c7ffbd318a80c16e18adffd147bedfd1febbf4f9bf7ca7fe59b4aa84324835834cc5ac484141f2526ec7bf8ffd41cb91a6b4fb562d3a0" + "cf5dca498406237677db96b300dd153478137629e33d6be4547ca5c58322e2e2cdbe2554ee3517b6d55c6da370dd7958131786c920913591" + "9e372355300a34153ededdf490c125f68399edaa8a2d8824d088b2242406aed1c32879a288c9e57aaf3dd1ccf1049b9c8ecc013b899cd25f" + "da3ccb0ce26c06afe6704486f5ccee1250f87816bdb728451941b4bc23be69a78bd732c3fe5fa5ae74f97e0c39c2596e1f7a63118bc2a05d" + "51e6139b9a18b5e05f6f41e3390c9d3650bf3d1ea2d64ff3f016a841523ab25b6a489dc033546d0197a2be0e5c91b35b7f86d4480fbfdb5c" + "ab57b0d797a7570a626642b672130d02b8abff928cbc608f716a3a61fe0178b3c7dbaec2b8b785ccdb4291f0bcf468940f5084959e6c97c0" + "93b968022098e44f72b733e86664e40af6c06b62f39816ecf61a753266e8b5ed55ef738d31ace2e5067043a93241ee8ec22b4cd7b71deaa5" + "dbdc9f058254f3ea3884ba4fe33c3e2835a73f28ddb02131c9ac4b96fc334a9dbbd9aa954916cd83cc38d32ecf5d151acc63590ea97c5e36" + "7d53f8c696e67c86dd00a5c69ddb3fa6c564264cd55436359c06c19895d6cdbe18798f438496a01441a4917f067d73bc44c31bfd75bd1afd" + "d6974f888413bae0100567d2416990b489a23b9d6026fa1c80793803493850e9f4f103a48c9a0bc95180fbaf49deef7a14d7e0770eb76448" + "625ada8074666a6957e2c5ba4bf9e052e40ded3aa4d09e0e26f87cb5330be34d29f1a5b2474e3b06bdcf9bb73ef319bba4b5e04707e87154" + "48c56af97a585b7ff4b40b186785ff2f4d58a6c2084904a68efea64cc5b05836ee1dff82c42ac4493c57ce1736da3f87f4336b57132a43ae" + "1aeb96f847d62eb36d2644f5f66b7e23e638392c9319fb0d05849340e772dfa61170a6dd6f64f7da9c42e984cf669f46a3c2efb2e964401a" + "d47fca051249315b0ffb602e6231aec6e10bacb09a2c1ac275c7614a740d87345d54440b7440618a0eb4e7b0d34c38609844057e80042280" + "042245000578b508400040066c1a0a2d00010a2d0003145196440dc78fbdf4439577801801fdf0a600000101080a7e069e553740bf25133e" + "c28380fbc86e6046d00e227bfeda912f0734082420811ebaaa0d46b71ef639010128de548cfa4bd17084ad4cfd3412c972384332e6069682" + "2ce8c8bd03697b1e6db8257b516944122c04150c3d8c4226dce12997563cfab8a999546082a26cf0e2f5e30b6a7129d89d7c6ece10d09115" + "32c5e374baa59e7c9572f1494a1ed1b7fc704a616ff47a3fc472691800d2e466b2cf58fd43c8ea442e30e9e057d2b9bd8832c9d8052817e7" + "34d76c146a57772a6b9210ffaef44e948029745999e1e3cb315110614ff1fd96bc8e7dbededc52c6e46a0a22e14c64fe453ff3b08adae778" + "810a3dcf06cfcaf132faebf6eed0fae43c92fde6a7adf592223abc50d78f87e0bbc0cdc58f3bdba8e0b7bec5d192d8f8f2a5122e5c4b6ed8" + "4273c637035aebaf7581f97308bf241a44897fc951631e6a72aa207850799956d4ec069b8783bdb5b269b7cb5ef46aea4f1c759d6fda4ec7" + "fa31417d491ae465255619f9bbd02a34ef5fa1ffc1d458d0ffaa4b2f85c3e164a29407e4405fabf1c3744e69a08ea516de6d2cf28e949c12" + "5445fbffdb35745af37023d095d86d701347fa725b32c4c14af69b080d9d31babb072f75d51bd4bc5c798c35cd1690b7f92ff7dc81a9afe4" + "202ae5d331e022e993fac68bcddeadb7d81dfb80a867c2f740d70b9520baae66cbe4f465ed5df3789cb073b06c16a3e1bb0c31fe2e449223" + "dfff32d474b90756dfc3f3bc9798baf9ed2c5d9556ca45e1d4573248d4768a1a29c3671e84191b6569f0589eaa4da7d72f953d45cfc380e9" + "367f0b65877419f71008ba23133d4ab43a42ba792e9e2529de020e41759ead35a80401323bc161b2f970df16acdce0a8d9a2547589b13684" + "66a8ef226c100b848718c460580304bb365e936533dc868b73ae5d21adf9c0b836413f7aa0f41b616497975798d4effcd027da635deb9067" + "c7352383c81aad06a8f4dde55ae6bc5e9b8dfb0e73aec189f51e7d691985b753c8474a55a8f0f88725ea052232b60600b8c9e5ddf4ff5302" + "a99d283a9de75065ed41851ee6631dfafee6915602b9dc9b60aae7327113bf048ca581182121c3e0c31b78f44ae61201ccc8a6286c12aeb6" + "38caa3507e61d36a37deb3aff8c73e8b73c03519c6cdf5638a4b5f238c3f8663ebabde48a086d450c9bb237020a9a2d989e62745f98efe7d" + "5954cb58ed05d138aade6e8d24b0562a7f8f26d382c8e7c22ff4545587814d41c3898805e808adfcc4a25cb2b75629bc01a17f395fc7249c" + "b83891fdc212f7c69e5021758931fd5c370b674caa89f19aa2fa9dfa5f52e518a88bfc2c5ae5dcebee4944c86946d0d4da2f5ca05668ca19" + "9b011a822502d5a0734db7b7d17b3262a0271a01279af7134562635bfd1310a123f240a41e70a88d5daf1d40ed51c6f1593f44d47291a865" + "564eb2f1d4f52694a1687f16f345a78a4e15d5789fb60fdbcb361249d647c65f8b77fb8e7c32dec2a6e4d83513ac4a1d76efa6d66078ca46" + "265c1ea6d74f5c9674239514695a2d6d8645e114a6ac7bd4897b8bb880fe1ae70e4f333cd0e07cd495a4b284bfc8a630fed02c578fbf467b" + "33988e9929893385000c54c6ccb559a9feffa3ca2cf103ece9ab36220f8319e25d22de3185534c090c72fa37a6c168b4c0691c3f10166412" + "8893da7a5a549533ef720626dc12258ce513428f9f341379e2e83e235fe09421bd40a94b1b3bd56d11751b5c4e4d43c3df92149ac07e1881" + "1995512c1ec06818ba493c169047d7560462d2ae6c823d225b0ede7955e4f4db22f38bbe4c0c99805ad7cf7efddc1dcb68ff29ad655adc5d" + "b48a9635c460a63e09656b7d2a7ed3d8633f5f0baa94f5b5a97cc20d21536cfd23b663f54fcfcf4d85fd155f783383d1f54bdf178bd18141" + "1ef644057e80042380042345000578b509400040066c190a2d00010a2d0003145196440dc79501f4439577801001fd2b9400000101080a7e" + "069e553740bf255a0048245205b9248caff7f8b7155c42d1d8dc46e1f2aed823b2495eee0cfed00fe5cf3771fb7ecf68185661a71b3799ec" + "82bf5cd3c646d60b7bad57b04685950c72785a2df47457a020a8b0a8045bb0549692f595465c207776af259d754ba23e478bcd3700ecfb77" + "3592e219b9d4a631536c28c09a7072e2ef254a007bddaa526e95da594dc188f02b3419b3674b7be02886281b3dbfd35ca9f0c34bbae549fa" + "27ea5287df1a2a4e9becf0c38b1107b5854493f27574687fb7b7b89b714809e7b7bbf9b69393a430a01721116bcec2de4a1363d2336ec4e9" + "08a2c8abf9bf6bda8197c8de793692505e44b7ac6d1f962c71453964da38413720b92b2b4c79ef20a0ac011246fb78ee914b1abbb905dd10" + "b5c3d169d045d464b821431df1c0bfe040bbcb2402e2fd02c24be8d464f4eddbc5a43878a4709f4ba8a09761ae6aa8dc53f181ac264824fa" + "827001461625f289c5b25b3fc72ceb346f0abeeb084bfb8e95042efedec1bd84400ff8b70505d2b21a8456b2c21480ea8d5832ed4c975535" + "7693e848558710530333aade318759bc02527432fa9d2277bd275d0532d00e4b82dce5569e87426556298bfdc58fb823a9cbb82c612af06a" + "966d11221bebe6fd536dbb8ff173a93f6b8c11edfb1782f8afac8d835fe3dd3fd9ae2295c40f510d38d2f39e064136e17225f50ff6349268" + "2399e8344b1cb7066b98f1f3f99156d9cae29a42d5f3e6560350ececa558f648a73674a0b04493e458f8b075e3e39f2c14d79c0a330b6561" + "69b84cec386c6b18c73c07102d3ec4f6db7c2020ffe81a6487703fcdf00ed786542c841b37c38f5d15f1c146fe63df462a8b14608946dbf9" + "d5a51b3c6974837db4094fdf3956d0f7bb89db087b8a6473e4556f5e120adaf50489b3b6fa59f17e6d991585f6f0b1d4722d2ee5569dcac3" + "22415f316082e81f34a0c4d4eb698120470c641f2589415b9efdfda8f8d5c2e7885d3e427d44539120fa67403879e9090af2e37c1fc1eea2" + "51c56f6ef9a4738d10d27190a3c9628887409a0af60e7b6904d877078201f925034c027e58d06eaadad9d30e7ed842cb000475f67e1af926" + "ea7cb95e2ed990b4a92bfe45be1a6fb5856885f2c1bcd4ecf7d30b234cf4aa57510ba9ea5615d81f4f5233e1ef8113e461fb4b4f33f0d641" + "e4bd72c34fb822b7631d31e44d5ce2fa0e0daff23ade30a173fb21c9ef1ab72d810a13b979c50108e7a616b3571f037618c5e19be218bcae" + "df349a42174dd455ecc459039eea00123f8fd940d320ec1a3a6e793c8fa8202a36ec661caee265b99323cb67970048b1b0c92370339ab5a6" + "3f92ba26513e17958091af1c950c2c29d7d9e2b21d70d96f19dc113199a748efb00af71e7bfc217d7167cca345a3b2751cf9afe643cadcc6" + "3c846a78ed2d70a7ff54549f9e753010a88046de11158d2f201c42b877fa8d871b1ed8cdc7af523f553bbc4b754031bddbec28f081db2396" + "c6af0300e6b9b364d04198998c5cb3d2d816b82bb72292625e8df7d8912426eca4ae7753ccabe938c174229df27f745c9163c547bd5b562b" + "0f2df75e73a4ff56bddfc660b77d51decc1224658a88e36d587f4e3a0bb700c4efba3b2b015a3857c25e50603ee93060f8dbf262f38b2ddc" + "23e50e63f0d765de27feb4234acf5024582186e017f54efd8c200690bd04982b0fdfb98c2d5142db1d7db49e677d0e2022d801bede20d590" + "2c34644df3f81e777043ad47b4996b11a4727854b0d20d48adc516d26372d15293605e439c4efec98425e82d3d9757d2b95e46ed9d6cd0cd" + "581d986d55b396c3c7b5af049015b145eff9316829f4ba82636c0f5cfd64e6d84e3304bc22ad93c7f419d528a990b66ae71a563e2140d6b2" + "1e77cc6212e46abb03909c44057e80042480042445000578b50a400040066c180a2d00010a2d0003145196440dc79a45f4439577801001fd" + "ddb200000101080a7e069e553740bf2560716ece116fe44e5908e49c6dc7d0344b8b66b78ba472d996cb49805c3b6180fb6ff71fee8fe5b9" + "9e322aa2b28b2fd5b890ddf3b773098f92fe06e1fc68dcf47f18a952d08cbf8410c690843fe10f3dd08c3d55843c7de21f785becd9cf9cf2" + "88582026f24399c8b184e3590087d9cdc7e61f660e92f9d242ac00e401012714d82457be817d6e5385b59ab4b593cfee2f8372bec4439575" + "6f2c1ce4c5e9bd86fbf2928aca6a96e05211993d555186c7d34ffca2fde8894e78de7ed273af95e65ab0205efb8a43b6919851ff0722f160" + "eda4b2eda8214454315cfea01100db6e36ad2670f3eadf2c04b2e5e023287f95dc7fa5583b4d7d967016678f79438836989786b3e87802e0" + "bae34dc9dc60d50faf8eb1717c2360c4b2b4ed2196c57e1c87493c5b30b7cba943acf997db34b58ac7ed788adcb14575080716303a2c8d82" + "02fe70faa498d1d1a1da0751a853e63e966bf4fc06570910d36df079fed97f6a7dea4b09fd1c7464500d14e8be5b36620b5331329def6ce8" + "ed7eebc3fc85bc316e4b47b8b4996172586a4cf41423da8361e8f3f1eaa2096966da7a086752d207141f098b1b622ab3191b5e59bf1208fc" + "2e63d52f712e1201a78e6705a7fffa1c4bae3bdf336a852cc8cbd2124152fc6cfc4d690e28fa5f3dbf667599fabb3a3d524f88030973ace5" + "6cae4c57cbb829f76e189fbc3f743521e5fe75f782b0619bc313e4d789e2b0f3f37ea443bce572eddce722c9949527949af00b40ba30a332" + "c6fd0dd96aebfca81e29c5dafb73d99aec483ad380d92e08798846ec67519661a79ef63e0c37c93208823aefc4a8e6f313e4c54c623d6fa4" + "f6bc3216110aa3e457d05864f0fd89b3f2da57aad3cdc05c5e4dc73549d80a61cc3b1f5171aca79b35c1f1eda3b113fc635d07b89233c46d" + "577a1a4f8243ba23f5c2b57be08ca992d14e43b81c277ed19b85b903269dc877f0d18cd69c7ef7b7ef8cf2f461c5afbc9f9b679adee58e53" + "651f1bd28dfd3480171cabec6aacf1919ad803fec869345bddaf8fd4534333ceb64c028d0c17645dab653263ecd09aa9add9639544f0559b" + "9413d98df6ee4ad22ecd690bd15f6881f98afe55d56847a1710cb020a67f22f567f7dba73dc9c9c8e31d1733c1a0654f5b49f75be9cc2cb6" + "401aba58906c5b1b4da97d40bfbecfa55fe09783b560b5cc3dc28f72f43e167c5e8dcc9836515111657cc7743674e8c6d38595e8f1860a57" + "ef23a95e58340cfa3e60c784ceaac7612de4c0789f9baa2674387270d1fc2fa522a5246f4e6bf53ab935a87e5f790f29b083c10841c3720a" + "233f21257eeee31a8b987d39a16278b302869afb3340f9dbcf07dbaaebb88a6f143adcc1273d44997a12c52322b65995c086e557493709c2" + "990142ca039aa5de8ebeedebc482143c2154953c79a59c0777238479bc934c2a35278759813df1daa70b1f508560c1aae54633bd81964949" + "e6283a646c810fbdabab67030e7dde8e2f3f8e974b63884e4a62ea58477d79dc2599b98fff4ab84f6f5a7312e3f4c71727b00930782c59d9" + "366cd99e38f71088d86d22863be20e88fad8319608a882ad8b2a4b494a7a55fce46de429b5b511091f7ca2c5fdf560ba937f00093d73e77b" + "aec14ba65884374e4d73dec271748d1da9802d9f06c2d4ddb8faedf50964f7ecdd10d850f9e62a3fcd1cf8382b0f3a101d68cb8073291c46" + "9b16ed8ead94a0f21b663c712c299acec7fcd3b3dc0611cd7722979fabc089a03bc9d5d5459d08c53cb479c16360d6c5a31681657806f331" + "77e34e972eea75fa843a68a8f0570acf84118cfa65293b26f4ef6c7393bd2549c1a946e0e01fa8fe6a2e94fd04bb24ada5fcbaf4124520a8" + "8c13f270eb548e079937495dc505ddf5583c9cba44057e80042580042545000578b50b400040066c170a2d00010a2d0003145196440dc79f" + "89f4439577801801fd7a8e00000101080a7e069e553740bf2517c92d2556716d2a974fe272b0c77803f94a04931bb9f4381f66da6e724817" + "316ecf5e9df3f873ef661bf3d97f0d0cf7f5811cde2881a3dfc33e25672b9f48043fa4211c856bcd0ad44179a979aca031b48635ec0f7d25" + "75c5fdd1d8f8c6f9e3d8ff2b6179947571bb6258c0dbe931fc33f2e0eda5f4a5d0d4c80fc71adf3c2843d890a21390685538c46bfde9cbea" + "7ef798973412122b30a61d7744103d813c58dfbaa93634fa390bd1adac7961c613021824a355a6ecfde1be8e3a75f8f284ac65fdf447d913" + "c595e09cf38659aa9d1fca56b83a7d616abecd29b757d74acb442cbc4d3fb5d51e35d05de2d9d0d1aa6ba7a8723d2839e65ac6d8d87b7fef" + "9e5d35373353dd34149f56d42b534311664f2ee1a612862f5190abebe4b19b30fc9c74f2aee0c85383fcdcfa1bdc00fc6caae32dcfd6118e" + "e503e1d1c0bcc5dffaf546c6c7c5db9276be64bd580f6c4e5d43c3c9d71c536a37e46b0b94977fba6a6b26e9f99ad8a79d53d5f0fd1cf87f" + "cc80d591f02fac555de83f9eef31eb95c1550d377143135fa9b1af56c4a37788b7221c4326ba6e85eddbfed8cde6adf29b6f469d25a2158e" + "a7f2a42357fae6b4cea6c563c90b0d38a482645bcb5ffb4444e938c7964a267443d7e4fad058b528b40739362f7964bdb452700cbde2f2e6" + "22530b87320dbe143ab63f080f8fd3e0604e8a2d34f032bc26cee5349316d71a0014fa6e42498e3da53d3139598cd45008bcca7146ed50a8" + "896217435a92c319696498d63693fc79f97f80a66ff0c8cdb1364ad1ec81b381fa05f2983113320fce491f3c405465ffbe9f93c29530aa28" + "e5ef36b5c6f4ae2d0268eb8bf2d3064c159651936ad0711eb4d93b712d1a433e220ca9c6a31a99fa3e15c5788062d76dceb279b4aa223dcf" + "8201bdb63f4cf4b6e89153856d01c3c681605f4112a9815cd4bf4676de50749cb9a34360da1e8c82f170a5be5b30405bbef03498bc8199a4" + "f602981894537eb9bb18034871a3d9f75ee0351f33950a7a90ba4cb9e886dba1778c5601517c97b43de02821f9de2d2d0901c76176c5270c" + "309cce1472c8b3e04fccf9c3553285ef97ef36c1fab2d9872a5eeddc4b1d6774a86ee42ba8d03d4d1ab3497b99eef7681affaee0ee05f1ee" + "368325b53d14685998cb1ceaa7aa83b2a206eb788de6612cc4689b337b5cb30a90db6e93c0ce8f1e87c64749bbfa94b30625220560511200" + "fc9d850d8349f3958177a8e7c0db6e918b712190658f56e49ddf771d535a3e10ee9d037be680364a55fa36dbaf306a4a69ca57ef96d0c99d" + "c09559b8414d08e842cbd7315233b22868d8e28d1876bc9118a515f28b8ce8ba1a87ea859f6c0839be57fdc0fe5ca7da5e7eafbb7610be46" + "e197f0d4b76cc88fa7bc31bc88cbb7c2b6860fb6dae60569f96045314ccbf7f8f9c2db289781cde585a2fe619f6b2895a0744e83a4a8f454" + "75a2af469e7cd9e29ab5269f372fb4a1dace9db078e55d8e4292c2d82a6486510e9408045a92375cd6bd3784d3189316118b0b893973d696" + "ffedd2f21a18dc75cc57e92601904ac070ac2638de0b93c9f415f36b714be6132b72ff80c9bcc25a2892953814fa5d108dc71e426a8d0810" + "63e4f98c7a498370e853bac5d7b0e34a1664b9977954cc54f72c9e117be2173589061f968e515a5728f8e9116c04d1311fbd4a99f20a2c7e" + "7e30403f2e5e6af5f1edb8d16903ba9c10b7e317fbaceaeab34e0d363814eb8e73fabc43d12d7a9f59dea1f2310b4c2edacc0dd6a3e472cd" + "1c34deb20610ccb2a3bb83d3a854a04f67167ec9902f8be601ef580d7944c53da98527e073653f23827cb2240e7e1a50b91c61e3655959e2" + "b27f585a8a6855250a8b7020f8bd3ad70733f8ac3b4a4e5b763b09e25344057e80042680042645000578b50c400040066c160a2d00010a2d" + "0003145196440dc7a4cdf4439577801001fd41fb00000101080a7e069e553740bf25f3b2cf587c3a0d805c02749c1658f48c2179d4af9723" + "dcba9ae7853969f0c2c1d29090aeb5a93d152e3375b31cb07e670e54473a1f099c8a1e4dea6bb1398d0ef034a455f73139650b9c81f0c4f9" + "7186684ac8d7a3a3382e14068503bb5c1d46e41d1b8aab4e0762ec09aa0a28431aacd2f358ebebe4bff753cb5580ca577f227252bde4e191" + "b8d752e77ab5e1ca81397067c8ad4cc42305ead8b8594973e129378b6fcfebfa2a2434bd4639bc2dd08627da8df874899524994352587852" + "747827f21f267ac867f0d5b187838759c8e21c4ccc5a62687f7abe988efde19378f19b91213b0602f0d780ccabcc5c66892e390f14bf8844" + "6722500eabde9c406f10eb81cfd09ed927ad0641a416dd7218e4ea765112dee1923b8e6fcf849fade0346df045fef523c4f94d010513d689" + "d4f1afad86cb164674390ea9c32cb60f653e5953ca953c3a1c9c580f90eb2d67f8ab03152be8210dff0c19cc08959d1eb30b63728ab607b9" + "0e0cd8715bd2921ac587be877c43686ec21c8a56b8fcfa3bf8c9e933f5e83ae5224ae09533adb9a8872abdddd3a40ca00ce84c63dcff36bf" + "4d0d3ce5a49e26f099bfcf0f8ca3c863f139cebc48d77c4fcef38336901166aa3d2ec52eb5e65a8f49c7b601ff93c61042688d6bf00d7959" + "5daa89ade8af0b33d3ea809c6697f37e92c0dd42b45f58da940d534a1f39cf8346c528d64f4e224ac9160f398769017178403cb4287a3ecf" + "aff1543503acf9b2d33ba69f125678011565af6f1259ab7dce10badd517818fb26320b0f27855d2bed0a4b870c8241d9c1cb566c4830988f" + "818ca2a09c0342f475982ecd79c6cdea4f9cbd3388ce451128b59a24dc34dc76065ae17ee02356c3f73bb8a62731eccd4dbe6c11b366e58c" + "789cb7ff1fa412a4d3c39adc3baedd2e75543d5af47de582099d443e382452a4bf6f0b01098b2df6a74226017c7a857d718233fec76981ee" + "aa51809e70b12604bdbe58140d683f4b233a7816ffc3c52788ea9a62074d180cbe55e06f5b2bbc64f4448020ff2c690a01e460105f289de8" + "efa237bb36e732726d065ed45b364e65aadcad483e4432ab36017630134b6c2e15ccd01d77686750f4716c109421f4dacc5e671e0a96a3dd" + "a032fe2dba0d24c8b3b8d1c7b735aa10949bc77cc8fffcfe079e121e5ea9a395b7ac07144dee4e17f25e0df419d0089124625eea887e25c0" + "749bbe1f6a939740d009da408a3abfd8765f776bccf49ef916da7ae6013318a0869eb9a2ee7dc5d18fa616d5aa47a89e12f0184327d14a70" + "f4e0520a10c35db213c1e176a5b8a28ccb9b3658c125f2afec473d74ce2a6f271895abc1dffe332843d552706f1723c478ed247348f2fff2" + "cb2a1f590ff1d9527fcda429811bb120c32b90b6f3c9d3a6b22ca7996d31c6898f02fb481f58f56057999543e2e9d296bfd41f55f5727bde" + "0b465a7205ed8aece49465ff2c5dead5204ff6eef34c79fdc022c9494bc8e37a5d51228f9029240d308f157fa29586aebe81f1cceb0805bd" + "fe65545386acc385fc4bff3025592fd86e0c618b17de2e20fcbca26de823ea2fa06c820f86c4ace9e48199dd5d1960292812adae90d1f4ce" + "f699427fca67288702e6f3428a7021966ba1f580a2ec8a50a8db84571e0243ba87137841801cc6341a5523aa2ca780a2adef96d6ff99f817" + "7257206c1b8ff6e11ece5694bb8e4dac6a0c2d208ca67fd7db35e8cf53b6eeffc2841c8c6a5293495c28a17b4c2c3bc82e4952d0b22068d0" + "7d28419c886cd46cbb4a67284a9b684fe94f7bb0a31caf435395b37fd5fb322bf8ed0fafe8641f1a48e600749aa04e7514d6090c4e6f1b69" + "ff270ee8d8466e9bafdb1b463c5d7b87d4f3b82cc9b9207fb15dca35f072b520b66bf5bbe15f44057e80042780042745000578b50d400040" + "066c150a2d00010a2d0003145196440dc7aa11f4439577801801fdd91a00000101080a7e069e553740bf2552d692a9ca74e85ae50d2409cd" + "f21b939c0a29d33a7074e3fb25c32bd63c1c1f35c35cf2924399741e0fb02a1c82d73fd6e911d791f30638db8a9c7da0839369b939f8c7a7" + "37057f67c3b241e6c0096c3da948772a03ca0e750fc9a4f3bc1cb622bb841bf190fb2d6b30a00e87bf61446233bf8a366152326610ca3b16" + "033ed60d552a6dbdea561904312e2aa9654f3fada8bf615a445b95cbbdbd62ddfa105eda2625baf4e72dd6bf5985aceb040bddeadfc3935c" + "6ee17d515c755e22c7d3fa52f524319f5eea821fada5ac6c5c680198733c23890d1ab36a3e37aaaf64a3980fe2e1fc9221a725955f6ae787" + "d93862b9769b8af4c41dbf0f35146c77886cea4203187d6de68dd59d773bca6e57b067e6f0cf9020900acd2d5b5790288729d0552da438b0" + "0248c0d34f668461f0cae1d86330db2a1711dd17da77cef2466440dec9edba3b969c57293d115a5c0437f0d051d2ae6636c01975b9bcea21" + "0eb56166af777dd58096bb4d2e70e400e56dcaad8ce168b4638aa24982c23db4355b5034487df21850422651bb541df5adcfd9077434a5fa" + "7968683100f2080da76b4914220cd8b62a467a8f4b74b16c52a08b9d1ca92b3baaa136e73b290dc5221965d2f439e2c473259e745c81fc09" + "f341bb36df316dc11c5727b649eb53fd6b6acf9fdad932cddd68f2e196f683afd04da86ea6854495ae45b55eecb057b66dc450b8f18eb6d9" + "f4002137e71df5aa9df2fc99b5d2763761bc828faabc0b204730a00fc318f78d6d5bc895192cd8df6d4d7b23a107aaed2583783bd1204714" + "90c757187f8edc7307dd011539f7fe17b8b1b22bead8f1d514452498deafb1d3af7e3924bbcdfd71e83050efab05e5d63665d986d17b592f" + "c8f251f7e6b12a4327944b92eead549df5d6675eafe749430bc7ae13ad389954f6069de6e6c8964e2d5434e74e468c46dade097470bbd58f" + "d61dfe0c42bf5b926f6af174fc08e7469e7bcc52b9757255c1f95c44400e4a8573e9a36fd2635d855b392bebafed0d23b6688bffc1b223e8" + "5df7133e30b50486ee5aa953d5bdac02f636218e3fbb998230b15f3dd7cc7b162d19b9498566feca3aa5ec62eab0d23f15d2d760f962be3f" + "04cda32fcadcb3982cdc91ed6b33905caa7c256c49311e8ce8218d813e7404ee4495198eaf1159c0348c7503a5ae9bcfe07a07c488013e98" + "a63f87eef515fd8ad1628973064f528381aa61590f91d0ac43f4094c85ed471049c8581a83dd78186517d3b111451370b8f18d77db8dd7ff" + "072ee97bd9b4eb40d4f34413380512b6cba71a8857c6ca819d438cdc24f2441a00de0b90b1a6ae2b9cd8be6411fad125b3a408fb825a8615" + "5973a0b1320f51a84d7fe480af857336873f6c891ec789d48db0c17fc813a061bedfc5d2622d05f31fa888f3b9cfd88e3f141dd81b376a93" + "1dd4dc0e8ad635d5fed4d327c463b498092fa6864faf737fa18597cef2b6b14379378f960ffd02e1535b90f6e7a244cede14d90d93b6ceb6" + "266bb7883ab72d70aad7541e2f2182fe17da7312f4052a6e86ffd92bafd1d951e6d9c5ddd164e10270e5e627c1cfdee474155a9691cf7c99" + "c90db11abfbd6317ec27585a5bb6c35ac063ba0bcf71799722922e0a8cb0cc4b99bf0df8d38b567ca2084b34e1fe28ac6be3499fb352e03b" + "9a16a616f3f234b39dd3708932adf988fe29c18cb75b00fdfcfc98a9109ccd0835efa04b4b71836cde77d42b8dadffd2b4974100b95a7da6" + "5ee8ca976db21eadf3535c06fba169ac0da12f8361adbd2c359e6da9d897dcac6f21f0902007f698a909d7d190fa9ae5269c5fa6799dd2d9" + "3bdfb6ea7de0c434608bb6d79b34763172233b4c026263959eae59294ccdd9b7e2fc35dd5a208fa076bfd7b87c0f1a44057e800428800428" + "45000578b50e400040066c140a2d00010a2d0003145196440dc7af55f4439577801001fd0ed500000101080a7e069e553740bf2576873716" + "8ba911b8aad1e5691f76eb44590460d12a085172df37c6c0a31bdb66e760dbb39c20827550480a32c6bbd4203c2eae369eba003c18047e80" + "f68c6818019fd47799737e65d762f24deb9d244b31f981a48f676be9bf603599ed5b1b2a323cdaa6b0266db1ce6c4e38baa974c79b91b931" + "c2e74bffa3cfaca592b2f5907903b2607cde85d5e629a4b9b298c6c382feabea51a3e1529fe4c93d14c19efeffdb259317fef77fa01173ce" + "ef95ed3f90dc4d1aa10d5d51d4b87b0caaa004163d1940ca93185514dc59d03a2a38fcbb81efc0aba0db4cee095d5b5987c42ea124391c70" + "a425ee833af4ff8f4bc14f097bf8e27d6e82d75875896798652bdc8a10774d12dff4db2aed1327e44ea7586674acaea77de90c5943f641b8" + "c6573ff1a9de67ef957e93f766c61b5872bc1f6c409749e0d12b17451186a9ef5d122c726505e147b0165ca9bbbb7d3e92a9b3c5988c1fa8" + "2b2ab63572c1be1901d120df48d0edc08602bcef396a9f641b8a29315d374e8c37a40b1024503379488db2088811ecab79210e694ad17f14" + "6b2a111cb967e111c5c9be1add0b76f1e6bea3c1d4ca359e517d6a77e07b6b043a3f93a16994807ddf3b9f5cd35048b358382beb5154f609" + "9941a778e6b91f5ac804c73e7c9869016724797eb58af2ce3365bde93daad532ae72e8273d1fdda73dc1ab9cb6a8d142a536f83a93a3d95a" + "7cb8aea041584ff65d027e9cee131e928bc4507341fe6865b91ed6b9001ea9fb4e648b5e68a99a3707d8af931c3b614f7e89d7810614272e" + "439a74f15bbda45eb665e3d15cbbb64b3166c051927a59d3521116e9025a9a6ff61342584cdee17fd04d62325f20a37f810085aba498dcf3" + "67e0c30d43c9f0cfdebe06f1d8552aea59fbecb8e62728f52f19059761474b7fe9095c04f1a271da3e74c907daf6797578940eff63ee3b3c" + "0ecdb255652ab316ce985477a8688f20d56419fc93f7e752d26279b07ba2a6937468f6fb24c9310a6f13a9c4daa763d9c0cc83c9be2148ac" + "9fd50c2d752c9d5c5cbfc91c793a35950b76475d8ef615cf888369bac922727e6fe2c76c2b12fd855564a076ef13236c5adc3f4b1852ed68" + "b19f21b4b92c5c0a7839a331bcbc3d0e2761523d154e1ce2f76781056268ae11a67d831ff6f96a946280bbe67096f1c02f94074fcb3a73e7" + "dfc46ef6c55e6c5fe5ec311a718892e636d91d4889a54511392d1c46207d6dbb26f10e99797504cac4ee6f0d11a7a2fd24bb7e3be7468555" + "8997ae1a3b518e26f592d2e74b5e545cd83fb587e0dd248fc76bf94958819e3c5675d5ec54a7f428b42aa272d155b10f4bd2f2d237135934" + "b5c7e79d321e31d63f0dcd5149ff95fca67ce0dcefe2d25a3f825950d19af464bb648582d408b3ca36cf9302b3d6806bc52c821762300f88" + "984b5932cd65423e349bb2c19503dfb768e6df5f6641f4a457327015f6bc1281fb0d28cd5bf51d5fb602ac56ce4e79283b5df2fd753fb201" + "900b1f78b2236dee983d13178ac4c46812c9166078d5fa0f059339fbfde05160a0065906c31db6a22c733e3a4dcc3f07e4d4aee0bf9c74ab" + "b29c8a8d22da79621c815c607aec65065505380bf9421445b19ece70d1c9f07f83eeb8d25831fe5961c9247fc75607c5cdf426fad8c905be" + "8e253d02f0563255efc4b9de89773ccd38c9fcc81f43811661ca90c22fb100b87254845c7c5e5415307ea4355f8ab55b60b3f6a42edde203" + "8b0890f15b2a8ca73a8a4ad30099c0a779525ffeb972ed7fdf53a599e1e41fb944af96f1c3302910d3b30cb18f8fe18c54ad31e7201c8847" + "817df67ff2346cb34826fb12482804ccefb46297d4f3f1e9a29f3cdf1be901beb5bc28457838387b33d49861915ffcaf9471520a4a88275d" + "44057e80042980042945000578b50f400040066c130a2d00010a2d0003145196440dc7b499f4439577801801fdd59300000101080a7e069e" + "553740bf256e9caec313cb79a644cfc509c6025f557f000902f99ccac1aace4083fbf8478d094770be36356e2dc5bf235486d148cacb73c6" + "db8d346e74928f238798e9597505e1aa58ffd4b1acfbff1c9ba3c0a73496bc6c77f114dadcbf928dc5a45dce358c2faf7e8ebad82c911dbe" + "b4dd948a1ed1609bd29e37339e865f471e1aad7243c743937d74ef4b68dd1c140544f05a6b3a75dfa1dc0641e26cd4aae81d73b7d2db0c2d" + "00b83a71369e1e5e38edf5bcc2cdd93bbb2083d80c8b858190dc8a6b5072179b996e4bced210dfe54865f32c8d21b58ec3fd6bdb2f7eff12" + "7d3a16a0aff9aed04bbcc9c568bd8c60282e511695fb7bd63b070a81d904459520aa4868e81509a3bbeaba5e1bc5ddb769b0f5a0407aa7ff" + "642e991d370c37419f87caecc34f7c0e9151624067a8d62b6d18f9bc4abe5bbdb01621eff39015b160e2ede70d70bafa4867bd302b140756" + "063605ce2648d08d7c1584d0fb1981224da8c346d33cdab4497bf8f2ebb4631cc7cedb1ca4dd2d31afd164454579d307fb6e60ef56422e7b" + "c82648be02dd60b0a5cae483a432dc7a9f9e339f3f86d84fd3c5745aa3bbdeb67115799e74ca825d4022bcc2e666502c26083f949c372a77" + "52dae5f2e1c908a4a1702fed188730ee537493cddef8cf7feba102486be1ce966ee34c627b5382a45ec00bd185b0d2f7548ddd421b4fa995" + "13851c97c4a5bc24f7496a903f226a3d0cc89b3faea32af966aa7b64d7cd6e985c8d78a96081752b14edc0de777dd1e7e77ea86cf8277eb3" + "250edd3e59424b7fe31c2510bcf3b04aedbaa91eed844122f113cada3b73918a8c6369d054e9228cb3a413e11bbe8ebfadb280f1de41be9f" + "ab9017133af8e01ffacba53b0a1b1d1ed82a55620e10f3c6588dee193dd88efc99a3a13fa3c8c510cef96329fe0c1847532c9697dfa51b2e" + "7dec0c89d4a195b15386360542145f800f34c6504ee068e9be48e10834a6d684ea5dbe5b1005fa6abc39b1783ad5f3179428ffd72507b30b" + "9b7d3ecb60d5e4a84c3e3f23c2d89b9269c9fe6004dfae042cc86ba495ed4160c4adf2509f24abf90ff578b7a007beb732c2c7d4030547b0" + "c0f8d9c4cb480a8c2dbe0af3429f59e2745b31274319b37fd68a0e2fea9144203c6e6c2aa440248f3d9554d062acf8275e77885765646d87" + "f8419b03e1cd33d025f67a8bb05f41e5e79afba5e933d1dd9117b32e1dd06acb2b8215fa09429e150a0e93c10670c227e835ce9e1bc150aa" + "a8a589b62167bb1bfbec431e0605a7194cba5abde9f15bfe116d480e76c199cc36dbc8262b0f9b37228f6da5d6815aaa33c53757d656fcef" + "7f51cb25697e0f0589518479a0b0f06e8540b7b79c8b0c5f84cebf76a7bafb79c09cd70608204d87a16c986753b4667678d34d6b47279915" + "49afa0b872b66be6fa343e1861914cdc5d7194f1ee38997e58aea1b1a37127ac4aaf552e42dd29c7b2ec449888c005beefdd50b20a6c5d23" + "6c66b2bc5712ac2d220a7a6ab8274f3a3b8d7fc9897b0a4d7d2d527067ac27f004e97e2b5ec70629a75d418f67ca91d5840985ad39465a1a" + "8c5563f51cda8ead83970472e98b48f9113eb7d0de5b234da2505f80706954ee8fc34411aaa2643dca45ab654a11fa205521082d1c29a766" + "c9852431f53e4b9991c70c595887d297d66190907b358cbd8fb768af59421769cde354df070f824e863cbe9f3d3092b8e936e2af5b31cfd9" + "e5126451ab1c834aaeeedd0992f1631086bb8a96426e262e3051737636bce015108f609da528d2b59e244893404c85fa6b04944697cc5cfa" + "27d2ec47c79ce923dcec7e5c87ed1bfb341b34b7bb9f432b183102c3f250f11f5cfd652c307d82b251c761a073115cdd4729aff76829c203" + "f6986a9bfee364451744057e80042a80042a45000578b510400040066c120a2d00010a2d0003145196440dc7b9ddf4439577801001fdafa2" + "00000101080a7e069e553740bf28dc9a0daacd7eee4b1075395cefe971f9827027bb282d9c943c6157634ea85b762d06d5c3f0beeb08d5d5" + "c6c64f52b4f97981c0351817cc7d43a357cde71d01e990ea00111fbab88104de2ec10a950ca8d2732ffab94d159223ebb629c1712704f5a8" + "2de15a4829ed1cebde8236c8c85e6eea7d063bf623218023b5573e144d3a3d3e87dcfa88039eef15b416566b49f32cd28cf0c46fa70e35f5" + "50c0e588054e083b9d0e2ecc1e5997f31940aea9c6efa56a09f44f4646a4e9a41fba6f271dd5081cbe9c18e4eb565ee4c84e759fb7460960" + "fe46a4231fe85d81137490371ae9791b0af720133b112f892bb1319655b58c41c208196a06afbdb8b1cb9b1934b49c7de6654996b36544fa" + "1995c2146d9485dbfaca268df19e5addcdb081fd62b975c8c40e8d32a894ed9b217f08bc1150288b8c388ec89a03e38bad24ff081b33a7d4" + "cd73136574937e55b1988f3a7feec09717fdf7900013fbf90b9d382d56761fc9935fa6af177f87ca6249cfaa06a02884bd415fe975666ba3" + "78142c17e315f8e67d71ebea10f43690693d8f15930acd4192c282d8ae3b230518457246ff635b418e9d049e7f18ca25dc45972d0b27dbd9" + "6d4a45f59018830d561ce55287a43beb877f7dd07838ed9d65e5b06143b367340e24a25974d66bf464c6660ea8447f2fb940bca3484525ed" + "6d2a55109496a16b3fa3098702570a5017b6a0d92d8339c5a9d86a93a7eea74f06da506ccce3e582860ec890923df9bc0f0a06b9f3a7d2fe" + "ad9f473a950b5ffe76b9ce70896adc1888290e3dbae0432b86a4c77aa9100f2a5f1914164f108c58b38083dee6d13163ec3d7b11012b025c" + "e35987351c157c5d7c9e6be31e6227f1d1b0b725a6188f710a3fad726043be62b51cf2510bb42ff3ea4f276190833689ee7fb8e054f35af8" + "cd70118d9edb9bbfd578275725380f286faef76e0e804d719d7fd0d7ed70869011c81c2dd4e72bf4bcbae6353e495b5b277647b156aa88ee" + "b2f1cfac789f5a1476a7c7b45971917cb7c4837299798d901dd97716de35bd002aa9d6a96fe06578c03c253e5d79c1aa8de8f2098c146406" + "0eabc3838f17bf6ab482b2bb8273017d45efe6fd61d3c45cd5d99bb7879906ac90cd7f310db5c6dd659d3cbb39cc83c14a2791b797dcb4db" + "3f6e09bf154c26a7b99055f05571763ac1b596908b70100f5f7bf9d213c109d52b532d507f6c6b2100ef9fe2a577d1208aa16824719078cc" + "abbc2e1165bcb0dc6909916c10edb5a84a3f3ad7aeb38f9950fc63793075ece33ca006c9f874a319efa6e8d05986d2912f5753a5cf792203" + "29b5518fe9fc40b0c3fccc70e8c4b625eb04df59fe4700caeda2fd6ab5dfe5704c7e39941a60641eb694d62116c96ac6cd18f26aa78c28f4" + "7aa26f5ea47a33403909a26200a5146381c7d91d249236f544532cb98ee84b687683df1aa5357aec12ddb9c87f062af6c7710201f0e41bd0" + "deecbf5d6e4848afb1925afe219c5371ed191c525a0e90cbab8c5e0fd56bf01cdd1e6a6dd7b260ade0b5c12ccc0b94de2930901879b2997d" + "c30a88621891c21870f4d30064080183b0b86b19a0b71ca7f7406f6423a46c54e0d345891e2d0e9fb0f17bdd4756063b58b997a9f557145f" + "adfa68f75fcb9b97e0c896b2accaa040e195e3ce11bdc15cc59b222e36cd6bcc8dda240ef3b80d76c3ca6d197f65d0f22f1a1e50b51d9f7a" + "7312b97bbc0ff7a18d8cb11ca0a471f219885ef7575c6fed15c0ca110c43e2a7caeb10b9b5a87a1b404a150133f3c681ee30f16bea14080f" + "967c4dd95a0078ef105480277c36aca1dfd713ff62b6578b6268cfe2e764e3967234a6864505c4545615ca12096b9befb66c2941a30c1d24" + "ef52a888f5dc697c96e98081aadd6c7218b744057e80042b80042b45000578b511400040066c110a2d00010a2d0003145196440dc7bf21f4" + "439577801801fde1d100000101080a7e069e553740bf286d06c3cf89c41d28071a658d0bc2016c84c4ad8429b6c8d3c3ae074d8ba2d71cb0" + "5ebfcbb27f1661ceacd610f0a02f6f01c9939470ebd5d3e8c7ca4702e1d1cfd6feacd2f992cf9371d40173b614bef5d98d5cf65feb1235ee" + "8d359aeadc6e6a09d4868dcffb2801558a312bc46584b607ab5a55e6b0d52d643aea790e2b47ee62c58ec9fcfd2fe214b79b7eaefaf06bfc" + "8928b7d17e164e2e9baebf3ec0b7d483f17ecd06a33b04eb5c70f3d77ce07bd4875767e0eeed24b2b002781e99f6892fc35a85cf0dd25399" + "4543367652a9a9ef0c01bd4622d159cf9950a274abc5e869258029a55284db9578489d9209af90d2b4e7557d3d8f587a375cf20cc0b1f652" + "5bc020ec3066ad96c93862453ddcf40cb30c7adccca32ab36d57ddbaf453f3f8ebb06960ec3b60a32d6396aedf5fd1e6b331317dbd7eb903" + "bb82aa03292c7651f733b146e9266e064e0292c5a7f98e936b688d1835a07fc2387998aabb6a5bdd5054de0ded8b67107f00f174d3c18b55" + "4c11939ff59f3d758949d187753e055656ff9bd99b29b0ea77483a288fc575b51238d3d682657d3dca7f1a42effd67bd1be0b062147e0ec5" + "fc823df361662aea3b51d47c17e7dba5d360abbea2c69304851199692030bc57cb9136fc638d258c8e5cac84dad40c76ae6846039284cf6d" + "b17be5097973154e3296e71e5e9b712dce6db02cdeb5dead8e5a5d0fce93ac403f0633337623b60753381f50a4081d5242a8c4d7eebfc016" + "66dd66cff50667d08e2857786f6848647dc248f71575483cc40bc54227999c600534b4af250a9b4a84917e5d49cff1fe39f8affb7df0158a" + "27b975fea7ad900447aabf7facca9e63a72b5113a73547c443f33184cf2dab6cd7e49f1bc4c832ce9f1b44477b93d10b6ccbc8b583446705" + "84f2514782b6c2d79b974f7b583960c390fb442d9e1d4f40d3445d02263a096c3409f69e303d452ba332c297f4edc999f7c1f37bb1edb85a" + "ebf968052c882c31ef7ea9b55905a76b528eef0012292b1dbcf287f15bb95b4c7fe8f0c68341370cc0ca907a9513f7cb6810089f99af8c33" + "7d84b8c3eb9b2c3982557258c0d4e8878a46260cfbbea44b195f2c08425e4788becbbfa77c5a61848036a69468d1b1ff12e8a164e110c4f9" + "224ef92bf949d48bf56ab326337f37f97c8e456ba50478b9ab07e2efb234a59878c341331c534199c6d0abfefca520082c738b5a28210d4e" + "f4c55edd5aeadeb13ff5d4745a3ecddb187aaf2b6a86b538f6bcfe79bec6cb2c74a5cefd3f6088c68cb53e9489d32a04c51a0577609466e5" + "757586cfd44c90719920a8e6dd62fb511bd013d4d49b44c6fede8bd3bc952c461df78a87a857461296a307b78449a18490fe2a02a574c54b" + "9054dcfe10ffe737493fa69cab8312c8b495001e6b7d379956b71389abbd0d6d4d9afbd235d969f483df1323753073901f0e30d15368c45d" + "3fe98c853be3630fb7b80460524b6a2890daf4278a24fb292e2f7c9f78243593782248c8b2d12d90b5db5d9a52f3eedb7c7c57e5061a6c73" + "b00ded8432609ab28e4f39ad69cb8583b5343680c021c82c6fb9ff63491e250ae0211b6626bc9d24d33e80421d1368ad0223c204602a1cf3" + "5e472ed07e1c1a92c70df114fbbe17542c1b665963883150218d2fb5f0843642b3f550cf66731118cca1f9cada1612da9c039a51f4db344c" + "60e431b3814df9191381fe03d1d4f6abb34408de14beb783f54419777e3c874ac1b72c9826fcfc53fabac402fbf568e1c09af793b3ac0c52" + "83b4c143e31d0de01b17ac376b2519098385240e7471cbd8c9c1ceb80c0bda10076475586a90516739fdec68502afe7392e953e8e2cd0887" + "ffb7d264f2f4705a15b78f98fb2185fca056027db3505cd231caf944057e80042c80042c45000578b512400040066c100a2d00010a2d0003" + "145196440dc7c465f4439577801001fdfa0900000101080a7e069e553740bf28624ee79d2d8b044fd685ef055dac14c406b8e5fe70b9d78f" + "d075d246f2899d48e8f2658280b037d34bf5927a91e5e1ef2b4707d8de0f4ab3e8ce35ede8bef2b5381b32e79add6b418815acbe0fbfd4a2" + "389277a32931ff3d2f2d0e7938c18839269b77612be3d463059e1d561c7bd60c97c922fcd4cb492bf77b2832764bbc9822b09c36ed32ad43" + "4745e7a89e61aa19f1ced30fce83b9172da6b1dcee2fef78d806f1fcf8fb76be3fd41afa6cb040d5c40fc3d75aa97e74e812a0f920881999" + "4c782788a96370753b89efbdaf3cab58ac0399bc6d59b98d5f755240187ef94b4d85db6c23936b60446c1153ef6eaa9d690e19e8b321b2c2" + "8181ae4e4e338c976355efd6780fd8d1527b2ece5e018d32bfa89a2572e80bb1629b52cacaafcd45721338ee6a692caf3d5ade4f9c921bf4" + "8b82cea7fdcf26e8861ccc82a6abe77afc43a3b52b19e8e3ea5a6dd48bb47c614f11e131c907fe3eb4eaf62d37759954f59bdc3085d22e00" + "9fb7e1996f5ace1e62000fbf8e63b4ba6e0395876f42eb485df9cb9946bdbbd42f7b6454badb4f0043c4649ecd3691a3563e365c0e4ae841" + "43d757df3504c724895b222d8568a548b628b7f400f09acc5c3037e866863e8a572692038fd3a7f0b4648dfff431fece1fbc16c9ab66095c" + "a07d08e86630a6ce3966a563a1549fb9b6ea737123fb0d6be4d8b60feb46d584ae11a7cc6df98ebf15d000f39726cda6db3c99b6e57a4594" + "5090996d58580f85e936c85bf41997eab263937d4cb91ddd3e5e618a160a2f34f91273fda45051ad2a2fdc585a2120e5d09fda04010b8a4c" + "b54636a9d8de997b9c0ecbec8e82701bc1e9d18e219efc39e4dfdf6ad6f60b71487b7ab12b61fa74e0bcb2356111baa0c991fc4ffcff9fbf" + "f957ae62e39a6d911fc12992cc2afe71450014794a2b134fc37e848161d01526d2c11dbe2b2581f17a912f0ee8d8e57e9bbc98e03393518b" + "b72ee83d1180ab999bc6116d1ffe6222f0f18b9bc1b3150b52f5909633737a06e1ade1c239caef149a66602f7fbe3e49cc407b10229568ab" + "b3f3a0edf3f19e21367f870449fe433223e0bfb63be430b5e3efca198f1c23d4b6c2d7b90aef2c584169ef22c209a1170f1ac58d1e60cbbd" + "6ec6e8a80235ceeb583174a8cf946d25d9e53990587c68c12f46dc6713d9d05e3ad3d781e61e76129e71e9ae5e70d0010726eb58d6c9ccce" + "3bd86a41161b6e572b1fc5d6e4cca5a41582867df1b72a580fe1f9d8b4c9ac3eb8932c1fce041ecb8518561477c0f2d3c13e13b035d64b8e" + "c7d659620843b474f271efc8d98d3ebb588bd6a01fc964f69e9521d916976bd9386ef63f331047bf20812fe6b514d27b514a57a6451b26ad" + "7f26695aa19a6df807ea93e095daaebfb20a28ad94a2635a292de9a22ccf68f94dd4c74540db868fb8a33a061f50ac77b093683dec34929c" + "7b1da1e0a670d88cecb710d2adb03f23aae11fa578bdc615f7f5a661d6bacd1117da701a5c641f0dce7273f756ab98699e189d88a1513554" + "8f2c9d8581d86099e8e7b20cb45f451bfe1fcad209171be9095b948d2769a6caeb127285ad4f5405010915c3500db6b978fea1014923c4b7" + "53121aae1e05513e7c998872b08016e889ea191d721730bf4714458785c18f0c770eb714e50f2c3b1b62cf79af864d06a2633bcad338006f" + "cde1e772203e13457ba8aeca80d93bea34285118c98e2a698def66d088ad5d36f4d0c22058b92a208d77072a7ab59c4fbd3295869b791cfa" + "7325adc219aa617dbb49415c62740745497919f21989dbfe3efe27e948a581aa53b526722c1b3269963ba00c1653e9b92c03969ab1d89ba0" + "39cccde7e61bbcbad0d25de8e3b1e1f7e59b8c6d837f660bba8f911ca132fa8e211a074a44057e80042d80042d45000578b513400040066c" + "0f0a2d00010a2d0003145196440dc7c9a9f4439577801801fdc6f100000101080a7e069e553740bf2842caeb1585d908b950a399221d1ba3" + "e96439e689b9d46d98ae184d2ac6db54a70e5658b4bb61673aeaca38a8a25860e11cbefeccb9607dbd567cc9f0d07d59e2b86c134a19d426" + "888813c61fd1fbfcb829838f6954ed72881277d00a5c85cac951789f0e0ceab2986addcfbf6eb9ba966031ab5cbd1277deb6438f8d7c836b" + "72b83362b5295b4d1f100cebc6f99bf2acbe2319623b5849ea4f73bc61a3d8bb969749aefc2627be25f0cae587344920ded4e6c3127e2177" + "87252f8346858bc70eb78eba0a42ea5e889ec27112edbc9fc22af418b7fd5cc72d92e5d324220d489ce03b3cbba4bc42ab1458fa90cb8eba" + "9bf389a8597a938032e86d4f2252bc8194cf52f8728344eabc0081bb394a01af9f9496f4bc202132e1b18b5311c38c029bf7c362b409c7ee" + "149563cf5526255ea17f776e63bb06527874dbbacd5e6ea362ca69338d1f704c43c7c84fc876f93caa89e3bb0b2952e92a21112f984e4743" + "e7405560005327443c82bbe538af313d4e45a60d56bcd4d5db54660ace09483205299f274556014f4e99f5c1e01bff35e76319e72c22ab8f" + "b40c1a90a4ea97a71df7f95e6f35ad78bd3638e77c14623897f4525085da241e4ba28bf56d8695080487a17f483c27b775686605f285a18b" + "5945cfc6d5eeab78890f8c6f4d2ff045bb69e98cf5df1f2ab3b199f6d3b17c470b3b7670b95c881e63b7c175e53d93de6a0e1530a7688bf7" + "e192f1790cb3192480e1ef30740b7a3de3f9c48be91df00dbf8e6d0f4368b9ff3b615a2ce101e09485d5f777138dac0e98bfd6df6290fcc8" + "3c7a9423f7528230807e7d15367e58a2989970ecba4d1dbc9eaf324a2fddc9a1d4e81632946016f062e7026bf995f0644e875c286425a9ef" + "7383ea1ba0230f58da1d37d6ab706f587392bcb7079a7b18251a60e61003d8f96a294cc90d15ae4510c155acaee644919ab87b520263d04b" + "c661cc554ce6cb1746ff7820ac2ba6c48f469c4aee62f71d345d60db57bf4c62e94ef6c2d821101c7a6ceeda060c1d09121f282a5e8bfc5f" + "b7d473119eddcc7707afc17e012a551e7535b9e55e0b68c7bdb0dad7fbeb9ec5392cabd684b32f667cf4374731aceba3a3f0f52d1bf27a6e" + "30aa995daa58c8dd2754a3d8c365cd7ead0071c7334b2ea33e2c7449ce163771ef55cc78c718afd01907121c09a80145df03fda690429a97" + "5b438d321e5d81f38c6d17473878e6d0556596903b4ac4d6bd54b26d96fe476879bd9e857d88244ff7d7008ccaf1660314b17dd59343452b" + "c55651ff49538c6e63577250cafcb0b1e7957e85b39e3c2286e8de9da442fd73bd0031b7421ebe3dba8ba282adae227ef69310975a9cfa69" + "f7a53139de8b7472b4295f4fd43a021a0a40f9675abc8ac669ee6739d102c5c8453e432198980b512d78ffbadef5704033d18a2c269e0fce" + "94584bd08ddce5a28b5d4e3c67d2e665af92902de88d29f6ea9ae78e2597a2473de14c26af94a66d6ee248e1313018ea3333eb7fa34cd53c" + "80d6a2836592a944c1dc6317856f440283dee92dc973bb6d9adb68cbe4af46469e0df7409e298367f7e654e32a7a465b6848b953847104db" + "494aa85a38881a00d09b73ff303165f7433946529b2a910fb9ff96b1ebbfcee2f146457e62dda5fb76f6e57f672759eff30c4d281c474419" + "d3e4ccf9b55a1be3334fc222d624f3b24eb060f243430878dab39242abdb266d5a4ebd68225df80f315a9e150ad0a7269f2a50317b735188" + "aa4390e86b54666b84f2595770e098529652bb94a2e764e62915192dbef1979df378012c712277135bd109f7feb09e78b6eb5e62bad5eabe" + "20b9aeaa803c5467f8df094977029ddf728e0ef7ba48bc4cd3f07a3f403482cb017e8a173beb6997d514a2497d44057e80042e80042e4500" + "0578b514400040066c0e0a2d00010a2d0003145196440dc7ceedf4439577801001fd9f6d00000101080a7e069e553740bf28dc35f1a69967" + "9819b7ee5335818b7d4b0fa7b1c575226f757fc87ff49f3f49f609c30295377c1c76f7b452bd33f85363439c89329ed4e2b93f8d7b219b0a" + "1707f316c5913004c8a02906a257ed767eceb3c4deaa6ed49885efe7734100de2672524f83dfc12bd5d38681c68ac6dd2667e418744e5086" + "853c03a2ecbf71bc0a6fc18d6afc4147444191ce93b5eeb933c862f61873ab7f5fc52e7e7d0ff61331bed29162f16406a58b99a7fc75e7d8" + "9597d8c8453f1ae3cf38fddca569c365b921138d314c4010cd0cb245af7e89cc3199bd1cc2addf8fd19f1249041ca066c62d883477079628" + "c225a1e149e59fa9db488b3922841b3cade0f49804bb006170890bc736fee839b995bff2238003182fe3d2146188f0615491a563b4780687" + "651c2180338e7e48bb7e5bd066ce0f323b6efb141b31402b65b5cfe3b8262a19766808c48084a8fb9a0cc1b788dee51503823340359cfdc6" + "a01716ff5b08c39e8afa707aed1831fbe2f374e91064ed1f5c8fc0967cd0c1639d544c06dfa42cd5e9d28d15a1c9d09e84f1a1cc66452bff" + "5ece41874d84a9a9f36885453657cd5227a0c1f6521e6b981a0c5bb2d8dec353ba8845683fbc37451b324568d4b9528fdb3a0450de5c558c" + "a0e7c4d1de5efe0d5c84fee37989a8778cc01406966e9f0c59afdb3f945a8d4bc0a3eaa22b863385870e69b2809ff3642afced72d86d8aae" + "dab8620724b083ed63d900db5f7f248bd79303dbda488135a9f0dcce26407037d5b598c5a49ca676eb34bfd14ba885ecd05353fd29a632cc" + "cb444e9f5dbf5c16c91f9b16f9c79080391e6dd17320129c8ff2c5cf39f9228361cb78ee8d6c3c43dd45019cd7834e38dee9732ad7e7d602" + "7e736639c854861be96051ec86d17888aebc638b08fe3143cac241a968a6a40c510fba5640339664de8bbd24a92e60b11c55911e469a8cfc" + "1bdd462a77b7eb0a6ae71907e26cd0d991618e15a30d82719cef13d7897b375de55c9c8741441747947c4d7d905b442cf703d8c14510aa50" + "472491fce4b4629509c01c58bd5006507f695382cef4f2d16c2f50b5f7a2b0e2968a5971847cc931a3884e65017f6adfb258a59b0265f84d" + "ea32d213cd181937d9d79d71973dae66e4dea5694ac5d421f52b29fe4f54eac644cd25d046bb3c8039abd312f28e2b7f09d6fa1a68cc6084" + "90ec19da53f65dfae5ad54e1226b6dcbd78919f972f2c5c653d15ea5879e54abd1d18972c31ff83b5629170b50ef8e0e662e398c1185b9f6" + "6cd2a72af6a44d33b985b3f67e848338c70ae699f32537aae27f3bfe3f0d8550578bd2881dc00f9c9d6d5575ff9284435a19e8b6d0138e3c" + "4e235b18e33359417b00a1fb8bb04b043097cdbe187837260a12caae17c8b2b9a2c951ca09fe1bd5ca8ce4870e3115c4823ec44d53a797ed" + "2aa4f04ac5953086fe551ccdbccfba09ae766f48dbcd13ec761328fe8b2d7955963c39e4b688f32488e6a605ba51f1d347127b306c6c34ec" + "964a77dcf0c36ec3f06dc52b7da8ab0b8a160d3c176190ac2c3f92af1aa8159d444eed22145b6f6067ee1e5d5eb6426539f77f39a8c886c4" + "9aabfa61bbf74988fcccf0f674e5f01b8722ce5de68504738c14dbb9d665b37c2c82e1e8e545f8d7074eb79f393016373886efc511894097" + "d9e42b88c537e57d207f4dc2d05ac46ea1a2abcf52630eafc46b0f8fc435493d5f66f58da3c153e46843c8204bbd771b96152b429d5e5814" + "8d52c18780821db99fe87abe650efcd8b5f3a284e3648e77ef52ad0a300abb956875d1cb42b944a3809c5833fd21a70afe5c251e0e464dd3" + "ffe7216740f256d331b2f9645063e4e17a79a3905052b5976614de5ccfda74920e0cc6cb4ee97d6b57a82d82e5d79b0cd648df5facc24405" + "7ec0042f80042f45000578b515400040066c0d0a2d00010a2d0003145196440dc7d431f4439577801801fddddf00000101080a7e069e5537" + "40bf28773e58cd4c604bab0547fa1511fc25d10833c651fb906bc9d2af420e0b82cddcb9f9ddb47d76ea9a4050506f3cded63b9cde803c44" + "b17c769d6aa94a011bffc68e6dadc0df9fb3e49f6a77ef6198893f9324ff4b7c90734c428cc91d9e2ee38cf32cc71da968c97e3beeb9b047" + "3e1d39365e137cafedbe5c417b85c252741e58aea7463137bfbe80a25d34c769124d3be28304e95726ed9f7716cc027ccc5b56e0f4d308c3" + "685c2581a8edc263a3cb1b52494d4bf2c545759e704451801d00950fac8ec443195907658ea50720794b2282ca51a6bf020e595d74964757" + "e4b49a987d34fb2aef95cc2eaa241eaaff9eb6613f36d9e009bd834b10b4731961b49085b10e0928c8c5423ff00b677a9beed54820df5f4f" + "ab8cd6f11db0c24ef400472cbb9ee9ff53108d082732b3b11b6e468d90e2f36d231e98b8e9a43008d87a116a92c208ee8797dba79d403fdd" + "96f9490ffbc75f3e76ddad77b016da732a01841d401249d13aa16a81c63ff985abbb442b464c111fbddae09389380123aad6df536233e8b4" + "14b0159a6ca20ae0e720ff63b116a2405aa10990d801defa897cc96634efee38b8af62180e96b8be83d136a0877bde9051b1a2bdbb8f7512" + "431754e8b2fb54dcbab5916d92db3b149b052b3388c29298868076c0ea714251df5f0a5f6258b96be3844727542b713a890a885d76481d94" + "c2a7eb6a108299e841ce44aaf369607c76d7f87dd599b1152258be3ffa81a32e5f7143be792293976c0978487737b61f0a7596eee699f636" + "3670aba405453b24b3cff4611f4072f2a67598ac40c2c1b592371191f772b6552f67541ea53d695c39d26fe33693b45bd80721cd1f8eb9b5" + "1e4336cdfa5e0d2f9709d5dbe26233b9902c21c8f653e4e41c88da6096e19a153157cec7dbe0540d1ee0aa32099447fc211cd579cd60daec" + "9630072bee47cacb4e9115675f07716b422ce4e74399ef05178ca292acc0be45b5ab15707b5f97aa87e79652dc333f22673bbf632da1ad22" + "70a498c38569dfb64582a61e7182c26ac8e2c57c43b5cbbb07693d04e935d78427c0aa8b069da1375e376992b59688abd651603850bad0da" + "c6663ac5cee8c3209cd76148e957a50558ae2c3b6b8572a41c8c7ac2f8ce9114a857463ba914691ad8bf5fe38292a42e56f204ee585a3f65" + "1b4c0567b7774b71d732e1d8068ed69a4e674ef793c3cab6a551f889ed9b3006d34895c7a6401402d1acc1aa7270586e373c5e7a82a30efe" + "17f962ba26bb821cc5befe9c6a91987aad31f61e8f6087ff0d34be5e2573c8b4bf0f1e1fd2faae733ba6c50ef6aa4931211c2faa87279137" + "fb2b53a143ed8c41df2a79d4e67a16eb05172c36f779b9770a0944214a3d1986d47d3f3ebc072a00924295d1401a2df1dcf2a4b99b4b9691" + "38790f5e03576f1d8682ab69096b2e6af2aacd6f124da1e1690cb68fec8f0eb794bd623137422d012e7757bc0d7f9e673878265297341128" + "297df3bb2d0cb1165b2d793e8b002a956082734559c148cffca5ec7662d2b1f374a3a2278eae010f24d1464e66bf7722b4537253ba19798e" + "65d2e2e502560a50cce26265a8e87f27548cd0d19930d6d9ab0dabab2a63841de03c2654481183d4475ce01b9a67d4329bb0dd3220888132" + "deb6d5beb27a8986fdeb444f1158225187f4eb5ddb894b95e509842d7a510ea55e7833c6fb0d1ecb8436963ff9fd07c48faf5c99c3de9079" + "0e6ea6049d565c5837075c4f6a5c37f1c7f7a8803d42c413af43131a17e175a57e66cd143fcfa407c5db50d4248694fbccc83c3fb45c9601" + "dea656fab0226c9a988cdd1069e2baee4c8e894d42fe12ab4846e35ae480d260764e2cdf3107d3fc6f7da4acfbd1d6d5381492b9a33c5d61" + "923c0e4b69619e44057e80043080043045000578b516400040066c0c0a2d00010a2d0003145196440dc7d975f4439577801001fd98a80000" + "0101080a7e069e553740bf288238ea06a46c72ab564d017a4c08b91a1c4f7a782dd91f80607c02d2745e0180b44aa30a0cc671f18ca51859" + "d4e11557f879d1b29af721040f0c9cacc8ded1c50eb997aea391a11f8c0276e33ad606de7662923a5b01cf49a20dff8cf3023fd88c1fbe69" + "269b744a9a8e20a347236344073b14ad914f9f4221b52777f76dcbbd43cbb5a82df44d559c74e451bd2c24cadfc0b7e833204f1315403aef" + "62a304f82304b84f77de4d48ce50b1520e5b786adafe9fc6eaeca13d9ef1a2933849cb9c56db14ba136a7c54e96acf37a5d9fd112f42904a" + "091a46fa40f32f913c1d0b2b83159236bdc0a9508033c8f8b35064c23b9156813eec78cedeb9688aeef00ab2e3449686fc4d58774515d2a0" + "12b2ac19019388e6bc1877711cba435958debc8dff210c17aab93292fe5c67b4fef6a13675781b82f70abc98b8de762d33d4cb938cc88f42" + "68758a69f6e113f565422e20f20f29d643c55d099d8337178c133b8a31c3f36ea232e30c98482651daf548522aa4b1df78de56725e43752c" + "09322e5a4de6fb245d9c08435e6ebc471f2bb5512e36811e8891992fb3e27207dda17c249aa9bcb01e3554eebf5f63e7b148610bf5a238b0" + "df2922e5df520187d3f7e8a0b976aa31a084a248325ee61702a22d2ab5fa57e4b027ccbbeb505248676a774685ebe7831c0ebb3594dd7f12" + "9dd0821fb94ca13f252d988a0e0914001d2cb00e8e9a23c9fcc1a66662ff752f409d4cfcb0c8dfd2793e8e0bd5f172438de45c1b13ca1d83" + "58d1b903282de3fa4ad9b7011fda8ce8ece304794bd97359c4ce19bba977a6eea89049d86c7c1cc06228a715b23a748cf81696d4669bddab" + "40abb4c417e7c5c4c5a1e5c1efc702dc12b5d253aa044d8329843363dd81dd26731fb669687e54fbbfd8016c21265c775da0780fcebfcc0b" + "c5e7a0e48c8c6d0713588755400a59effc61fd36348b3f00a71e31f64aeccf200482ddc39ec00f4a4f034a9b8b339a315e9561369a581fd0" + "b44bf858af9fa68b1622416b5415496e4e26b63548d8e69802c2d8622f270a25b266baca0d332c4b9846cb4a39b2effdc8fb9c75e2c26c7b" + "e6e7c23fd331021c55ef77c4a5afe768fad65af7751b8c30c725a087b19bac74009741d3d07680dca2623d8517aabc55af6ee9954823c29a" + "56c0131d910b3473f267b89b04f029b06b1276624ff339d956db6780a3929a607fbbee6f3c353b8ed10ad2dfdf16b2abc0c015b2eb2fd002" + "d565e0630bdebca945be1607be386f57f8f35b39c6e46a23c1879fc1d932c23e09999ae50025b9f35659fe17d931e6800eda82cfe6f9b8c0" + "3c96f5183b07be34261c4e7f7795e6d3d52b42dd7f3c954e326781fde18b88d06cd518c31eb1164d7decef7a5cca3464599f10c733b92286" + "fae8ad78f077100924ea39dc4e631d4ff7876d66c714f03904d1ae49e1e29b72b9a9d15ed838b1278311b22433e1c0c3186f6f7e23ac91cc" + "49ff91a4ad6db8e67e9947d47e9b9ec3905e524dedfac7227b99b3376361b5991ef1f74341bb4e6de8bca540efb3897dab85e4cbd0c0ed99" + "fbe68167e476153476614a58658643eef6eb970be282248e88b3375457754a790c2ec5acc5fa42b63e664541f9f8a9d3d85fadd01c713bdd" + "064d53fe3dd83eea70c7a7073a3326088dd58bb28ae4496a02b209f300ab19510219c602e2cddc3e4e3b2b7eeb8bc872661ed78cb1b36e90" + "230e0a0b93de2680a59d3fe4075c13a44b68c84dc6c8b63cad59a7819600ce828e88ac467458ed9ea7cb2d712a3859255f9faaf011780ffd" + "80b18ddc712495b7dabc84d98ecf76e43574bcd7eb8b80d951c8ee63cd4ed27f900b245ceb5eb9bbc7b6e3fc984820ca6eadc2a18732d827" + "5f66fae800287e4333936c59f43386bd44057e80043180043145000578b517400040066c0b0a2d00010a2d0003145196440dc7deb9f44395" + "77801801fd139000000101080a7e069e553740bf284e062a28de595a65d6cbacecc766fe4da6eaecda5f19fb71b08c53240c4f66a2b8e124" + "18ce5030863e900d3c369b2f171d17e4548ce59d15bf298b96d18e2ff2d6383b7d72f682ba95b1c1aa12227a57322cd6f9d95cd03c43ff00" + "1651aac8f236385de69a8caf1618168d550cd605a9852c20fc7a2a4e01d5c0903397fa0cab0c54b3834b5b24eb2f6a48ecd0a37615e8de54" + "eb924e238f926becca30b06dc6756cb44187a39f3695f81f6134afe50a194e5b1bcafc9cbd9ea619e4f5b709e8e0470e649a019972bd9db4" + "2569a911f63a050c6fcd715c727cc1591a16abb4fa804e2fdc89705ea39ff8c65eee96e15b69eab6642e19072ce73e75a36fc476715861dd" + "c4dd435f109edbf7ed051216d9e6afbc6308d71c2e7b5687254370d2555380d2843d06c9bb8fade9a68ed47072255564b2e90dda2930523e" + "043c632967d19ece8cc2b833f7aa9de09916e077a1a491b51c532839646928fdef67f9ec2e9363477342879fb839d0cfc979d6c71fd20a84" + "a60e6fddd590cac6ad17a06f9ac4bd4c6f3398c9bd0eb145e62639aea66dcb9003fd9a434731d0545e84c8920f318e709ac4d62988696a30" + "0e1e840bd7cc19f0e9e12212015d9958354c5d26edff1ee5e6e9b04d3ca5defbd364c70903ae6d20fa5d5ddfdf681119666f5aa1d9edceb9" + "17266dd31c749ef137ccbbdaa0381796532257c331067ed4b69f8fb3032ebf763f108706fc24839a295de1133a9d73de5bd0646eef0de195" + "5850a21117d46ea4d19066c9c9af7864715f2215d2ec5df7ed660de71e18909812be6a144d6fbd760cd5f90373aa010ade8b6074d9051266" + "81c9f5b1b8d9ae697c6db8e859b5d1c2250046e767fe19033bcfd55f18ad89ff18e19f41dd3917aec403a2518fd4bd19a7eef3afec0e32ac" + "37d19ee0435622bc95cb776988c4381196773cf6f26cbd2eb26e23db6929cd92c6c022d977c1f7c27e69853fb7b11b5e46ba4cf5b21e262b" + "4466844f61ad5fdda90d774b00d1d00d29ff4547e4f7d5b118ef0dc5236fe2de42a8639b9fa0b76af01e50b28eb671de500ad648264dc1b6" + "a050dbe2dbc03a69f49c0f96af37f142376c1062cf345fa41079d82fce0d3576cafda35a57240afbac64ab7ef4c1bfab2af7e987e84e4c51" + "7a1ca4063b04fd64ed2415bf7c57f29c55bc0cbd605844c78eef9187ee14213e05d370778108a5c15eda9faee4625f6d7565e7dc8851c248" + "a29964b166192945d1c9f5b929766025deb1ada77ceae4589c112e086789af32d5e27e3e3aba24d6104b692505dfab07157afc7bc5384749" + "10aebaa0ff429c831be046e383a019af8734ed4b81df2ffdce964fc4b81fe803e0215eae8c09c16baef7ba920230360ddddb4903a507ff47" + "568618de4202430872ae4384d9651c8df81f2515c2527b12eb77f9780139fb996b35ac405d0371f236ecd9ac77b666d9e6211f9a616d028d" + "0a4f85d8bc4436ccedcaf19a0f3013451c9c8f982277ab87c832cc9966a49e1904778491982697cda2eb9288dca570b61abcb01e6664cc0a" + "b6716e316aacbf7a249595a65259b9b90dd594c4e197ece10922810f345ad0c3d3f87ba745ac0a1015fbc0182d58362b2247809d38b18c4d" + "2b49abbf3eb45eb09b3be4b395bb62c1962560efebbe3125fb9d7f3d38ef442ad8d61589613c38143f427e97caa32941706a28d426a29365" + "f9957b989d87b37616a6d7f74323a61a09690527c88f882ab0c56c7dfcd439556d4b527df3bde157c527a93d577f8a5142d0e2716754c2c4" + "f12f6abfa12b842aeb7fdaeae54badc9174c9d47a93b5c25795cab8af2576a9104fc5798f0bbf07de22ee880ddbb436be765ee406efec7e5" + "a962ca57474919cd184cad8e8a23b86a951ea8b41e574ea7aa44057e80043280043245000578b518400040066c0a0a2d00010a2d00031451" + "96440dc7e3fdf4439577801001fdfebb00000101080a7e069e553740bf29315ecf5e7912acb807ca5a84480c7fe9889dc1978e13087db5bd" + "1981f81cf8f526d6ffcdf51ec8abe550b07b9d025d0b7dadb45c52f05d1c8f48e676b3cd3454262a33eed82f7adf6c23b7bfe4cd414917b6" + "0fbb9ebe2dc8d298f00ba9e7bb4eec362e11d3ec0ceaea534a5c8d764b3ce01f4feb89517c1c042e599ec27cc6670fe68638b1c85d79cf3b" + "53d9560f14a8ee0523f225e6b3af104319412b9efac207207bfcc3317262f5fe1e57c0b20cdc189db4f90db708a607aaf0ddff5b1bb86800" + "4fa3d848a4b326f1ebc1a40ae7c49a74d3b7358b3125b5efae207abe28fb3fd79108d57476f74c301d7bf552ec487b2fa6f1507ad845499d" + "8ac22902b6841845739f1b89b3ab32b031d3856f72f4a38561ca11c1948c1389f2e9378325b508d02fb1e9f9836bc0676dbde77c7b53fb6d" + "1d8d4eed4ff375d10e1682bea72265e3d00eae5846f61a25eb683e6f43d77c770257fb031a03c7d8302900c034b29265f59551c28d0a1463" + "b637aa52f31f726d86713f97d1da2452c3639c3ea4d6cf61ea4fdd81344e40a840db04c3e72a47fe414cb00eff11a7083088903d0e9b7aa9" + "7deb0374d154098c803371c4a3aaae04e1f62bc2a6ca2e9aab0fa5e641242bfc6692931b90512f384756cd4be36498cf63d043b22c624257" + "05742f1fce701c8ed5743de0b7caea12a8224f783b65301abf613dba6daa4b378f113105d762cdf2ff2d1195317ecf1ece0b4940cebbfea2" + "e577be41143c9b7cce4f0b450bf30f180d472da121d7c12535fddde0316da7233f6cee0857e455cb2d91740b5b458fbb07cb44f9ae62bc2d" + "c0bc33cbd9d45a0cfacde2a762485c9a1d998d9e222d8f307ac8e1448f85ed66ec6459e17970ad04b6d7221a7fe7a33e996367097bd9710c" + "0f2d857f06267ee17e170674bdd4298213c43ebc55e6c1440cd9b9d9145c4122aad64e401d41968aca9885139e78e56a44b5f9a197c93f5c" + "45c618dc56640a8af88bfd7123a6f43b00ff1f8b3dccef88fb86a0ba0ae10991115b6487b27bba7782a78256b76beff580fee0023a3fdf9c" + "a9afbd639d5ad2279d9ac1840425c4b15fd167ceec431dd8fa06e0ab7158203169c459e1a3853432dc6b9838d5416fdcc23508a70b8f63f5" + "2eab63e5749fc089bcc54dc378838650b618945550b2dbaa058783a8f6c6707d64a8b76b4f556e6b8e6d03a7adcbe25eb3ba01dd21ae60af" + "313946963028798afc4b3ef69f0fc93a75e9715dce0550916413c78c7ffa744911d04a58a2367450b8b29680d9c75534e6e18ae523030881" + "8b483f20e2d5469c6d275d73cd09c1674c188c6c39208f4e787e93350637299c68c6918326752030de952c79b8dcf6996de74d81a2a29d98" + "ccf0c6585fc1abd1157505521c151e6c54809a6b809ab76c150d395b641dfffcbb389b68c32eb1144f4f95b4703124aba8837baa41ef08cf" + "f415154201857ca7bfe6f62418f78f76b51aec06d90ab8b8f677aebedb7a453e5d79b514a6875162368f065960e023d498b436b5a3a8ddd1" + "42f907b0453dcfdaec7aa6862c9245fb47e19a9a20246da583b7f58fb4b2dfc8e3bd826563ef3e06125f2395f0a6fa6f16507c67a70d5508" + "7824aa0cd1364b3dd7e7e8913c6d37e5113356b4b13fe0a1b3e842b85db90e28247159a150a20c7969eb7dbf3f389fc33c6c48ddbfadbd37" + "7d5ac7b557e37d880bcfb486c1bc3c9b69b5977afa1f5c6806178dadf751edddf044f8b636b1411a70a0ed53486414c181e9186655c0f41a" + "19b20f170a7ddf8b63f4c58f35c3296074ce50380cf7fcc192858ce157c8913b24c9cc0a1baaef3821f0c3342bb12ae2e7e2a3da486797aa" + "17bbe3e13b97c261bc0f852a418a0a388262ca5f986e462ed4d26f1e5dc0cefe8cb744057e80043380043345000578b519400040066c090a" + "2d00010a2d0003145196440dc7e941f4439577801801fd175900000101080a7e069e553740bf295637fa0a13dac10c147eca8c0f11b2f4cf" + "011a7d538e331861b680ce8a37cdbc13efbdddd57cbfa9d02947e481f91f6fb2b3217c783f94718027958baaaa12ff58a1c3ae6fcefc7a2f" + "c99016206b1600a49713968e7e827332b286fcbd8c90cf2c69fab1e3b1445f4be8ab4938caeaca406f57ff6c86cae1d7b671accccbe0c1bc" + "3a2790df221d462772b17c237ff509ce956f52c94b41acedac45940b7541d2c7e92e2e9e96614078d5e0345b186db21627b9963d0c948159" + "8c51e4f157245d3cf8952ddf9efb5569e588883ac6159648c6cfe9af3ceb6d407734053b58f54a2c598f7c081c98b1ecc0c5dbb4b75bc78d" + "14e2c2dc2eda9e035b8f5a944d764f86169dca70a071aa665a60feb44a845302ba63132b1740fcb02d99846050313ddfd091917978fc7d54" + "cebd431e759a97b59f72bb0e8696b3684da6515bdef236d818b781f75285a3969c54bc4ab89df0ece93f7defdb883a01cf3d3f7abf54173f" + "e3c66dc2d3ee83a476586dbb1e2091ca678f01710a25cb4a33ba9497bd3b12d0e7edf793b3837980f7c828f50a7fc6afa8f8a13b6f8eb785" + "f7bd81dabea4d64314579900ee18cdd08d54bd5d21bd3a35be7992a8664a049a99e3a01514a7e90f3caed201176588fbe68e6867fea2f118" + "e0011c5eafd29b951939f22edcd6387b15685697a87bb9a044eea21585c26edc5518b1d7d861469a26534686a323aa0a7bce436191061987" + "ae951a902c59a000c06922f01184e91a6fcb6d187f446904ac84e04d095cd022e94cf6c2a3497c4714cd48f2591b368cdbdd52c74d50ff66" + "4a67fca3ff19459da417fffe61abc001edab8e6355cd1a133984afc1643f22eb7883ba9e1a1a5fbc98b9834ac0eebdcc8308dbefe85d77fa" + "4c8c224c7a549b7e555eb2aa5c2b7cd4ca3f012475ce57e6539e0c7f1f763980763910444dc8fd59d97e56c48e85e22ffe77c52cccb33592" + "ecab6e706bb29639ea20e7ccb6e809988c60ce73528b474befbdf52ab5e3d02d9b252b5c421b6805c69451c9436551ead2e37790b721c861" + "ececcf603d03546afe82e0f5d9b1e3595c2efba648235adcdaa951d1cb154ac0353285c71653754eb5564a476a2ef431ba295c0ee35c9ab6" + "7719934316dcd48a5e6402e6c856ddc450a94133f4aa8263cdeaa3b98dffa9482f2f0b82d0857eb4861c6ecb7af37d7d6906d51e8da9de81" + "c818d0b74aa7ed8ef40fd86d7637b7f6f07359649b261c4feef1d8c762bd0ec36ee2d0f015f5c23147e138e370d6776c08fe4558f462053d" + "dc66486cbf4a461de4b6d3f48de1bd5adcaf73e5d6e7d7029a45e1bb72c97701dcfba4a552a8e8db7a5607f27a95b321c4d28759174722cd" + "69103b4e2b2dec512e928e95b6a226c7ebb678a88acdbc6b12ccfc05edc553e019824699c3cd50e86599859a451d0b4b8d5dcddf7990f1bd" + "40fe7868f01dbe7f622be7cc00544e7c8f2f043f1abd8d54d349a2ae831df5dcd3d1c8d71980fec3fe0757ec150ab46c591534224bf9e81d" + "af6596f30ea927d15106d427ac011a94a9639adc1ce9640f228166fd9805a9bc26ceafe48df9b0162c61e08a94eb26457f7cbd1af6b95b7a" + "fc589e6a75c596fae5c697dbb2273bfc6ba93301a670e4f4150c59eb8fe03751d2fdb0e39bd39a895b442bf938236b1e470874c2380ad708" + "b292b80d231a78daedbdd1cd7ce5ac7dff62ba755e1f8e40a6486729defdbed283d4eab803854d460bf2800220b54105105d016d6f8c3711" + "6beac8e77c0337e751a6ec9d4027f5d82dbcef9455ba5a04d3e7dda839284256ad84d293e3e4d3144dcdc00fe9ba3ceb7dbda904d89659e2" + "2358d3cf158f29f932836d80c77ee7a8119754e420421614529e7a844ac51d061c517c09e1b93d832456a744057e80043480043445000578" + "b51a400040066c080a2d00010a2d0003145196440dc7ee85f4439577801001fd2ff100000101080a7e069e553740bf2999d3885c63243399" + "7b96e9cecfb5b2a1af1d509ca2c655d074fc1db853a0b48ce673bb7a39e55e853cad990babfa3f9a4a105032985dcf59f6abb471f671b428" + "4cc2a0d05f2bfb58b711288863a4c30fcb9407d0550066131704d03e94779531d360ff07bf376431f831a5fd3510d75ea6f96f365a7bdd59" + "41e9c823990cc8176976004fabac63e21b8b490ee9942fdd01031f73c33674d308c59abb6e364f8c24fbb01a6dcaea822119493da0572429" + "6ecf7eaccbdd916840ffecafebf9294190246ef775447d9717af411c683aed30dd00618836e323395d6841b1e331f8b1ca6acd3ceea33d74" + "79e52c7aec982375fd5a2196822f1af4237c91a850e248944f40acec78f58292dd9a5c7f2819af99156c6b612795cfdf92315a355de51cf0" + "2c1e4d26c36281b06e61c0e188d1814d35827d4268bf0c3cff9fb1b48c6e9e1746acfdc2662d97cef8a2de62e85613989c8ef817b79c8f93" + "b6836f57ce9ee2cb7391c416e026a55690ce5a163ef5e8472ba59f87f00f739f8d2629a60fa28c363594173fb4ddc569b8d13c059cf7fcf6" + "b6dc017db70384cbcdf5695f13f50b4d6d6a353b9fb0dfd25dea4dfa4f5d3b189ffbcbac49d8c8bd94112c065c22b03cc0c4ca8f50cc399a" + "51a00d0c76dd13a7540615caf5885bbc78e17aa9e86363a646634a5bcde41471ba9d30792e01a4a5af1e186bb011af5649dcdb59b44f3c67" + "cbd76f1d08f63d3f51a7f17047d16235decc310cefac66efc5d3757d17016262167a16fe3a0d92c2866a3233cc3e108c493f971279a3392c" + "aef8e5a50b04b6da0fbfc2c867de1292c94618675ace3c9c1e766b0367530217ad10f7a9954b65726f77ef4e3ead66b793c865cd87160c0d" + "f27a4fead65979f49b1cd972ebbead93ccce5e523de4073229a72543c2aac4ab0e5c5ec2efa60be82c8f520e1e4eb0e1753622c1eb0466bc" + "47fdbd33a02c6ff4169a54ffd04dcca7d274d949339afd58b75ca608aa450de2001f7c6ffc5200346d4bdb52ad88a4b096c9e73172c9bb85" + "8f7b427d56da11fbcc045cb9756fdcd19740d815d5975c53045cad2e9ccf373a613b06fa778db3ae98af38fdcc46b8dda3deeaf63a1699a2" + "4882e6d5f2174ad4b5c45d7846e300524657346e0cc6f888b88d540355e945cd37709016b33fe39cf0be40f1117dc4e60007c2597fa4b539" + "d866af371cab103da7103919ab79f5d1c6bcae2ce8677864bbe7569cb2b64320dc75d3fd15ffdd289575d6bf8276c90d5c12d4e335f3777e" + "e6094a019330a1f158ea1575af3923048f74faf56ab26475ceca937a380d5eb00b00fa4128f0a10320ff19ecf66869f7d8b1d1b15f0d9d84" + "469208ff490d855691046e2d07d6ebe116de9f33e389ca584f80274ee23be41a07425aa15b79c3e1178a78811a445f45cbd4f418481a9257" + "8d1a1d5bcf0f6e0f6cd48a49717afe12cc22ddc6052bd63be67093064fb8bc41c88f7df1e43b68d88da7495c41e2b19a2e32b15be4df1aa0" + "d35dfd9bf923d2b37ce9b9fac2512a4b0b4fba97af256ff0e6240298da8442057fe5fcf42e6eb6d4eea7dfc39cec0f2cae9d215bfcae7569" + "3b3e9a993f63a2bded024126688175bbfce19c886408dd4938064c64c7cd360fc7fbcb6a332de83ea9b2f5e9d07a8da140ab3ddee20ce2d8" + "a14180e33e60ce39176a20f1aa6801782859e3e7130d5332d6e43b55044476259960fe5505612d32b8374746986013849615cf99f497a4a0" + "4e44989b6020ec51e1c2092a30ae77f98501c37b7e43c10c9a314fe0035cf64ef7c5d5c4c1520d1a6bc81eee9023f5d9e351f03889b1ba8b" + "f14b804824020c1e7a9f844af9407aede9944181b7eacaf92a8045bd4d7e2650e13c261db7122829b8ed2597ee16fbeea884c71044057e80" + "043580043545000578b51b400040066c070a2d00010a2d0003145196440dc7f3c9f4439577801801fd4c2600000101080a7e069e553740bf" + "2994b610a7ceb3f6ce5d8a410fadf739699f62207c9e3ec403de9bd13d8029b12cc03fb90cc1be5d50c5f7ac63c5a654b66fbba0626d38ae" + "3b31db9ad0dc4e1ed2286ba98bb10e25b18b9f75099b0e0674bb2e45c8beea180b8d81cd96d77d1794b4d78fb9a65ed0a95126883befe779" + "0bafd4dddac0ee1228e95f588b741871d2c5613e6e4baa0f7abc34829b70f20e134b17492821564d9163f170bf5008a4de2708dba56f0c8b" + "8097cf50de6c7d9484ea19c6ee0f4fcd8af885e9b87a265f20276e405898908931c2a7997932fa06ea0fd3f40a88df5df19e1db69a17db1c" + "a8d0fd3eefbc3177a62c4a23b5b34525e61296ec0edc8be9c830dce8ab7f135bb3f4f1ebcee2a8f032a9fda5c3f622c999f54ff5fddd7a45" + "0255454c5fa3414fa27cf1783432eaa313016e8ddb0d638169277958342a596ae61ef1fedce40002ec048c4eec16e3f2e33d76289c63bca0" + "eb0c35dbca936e6b0d45605ec4df402e1e4265baa1edcb5b90ec35746a497bd5327011ecbb3df282bedd27d333b0fe7691b9a7d4d206482c" + "438af2dee0f26018af72c46b0b553d1ef5b10285811216ea0982fc6f5302033930d6b7937a169e7025dc5f4c3c7a27f076054371194b9d0a" + "c6c445bb65061138b8ef01bb3d410a56f0d679dd3ac100f8f3661d891a727a6b8125d1321f004d00ea859734c09aa0150f962c497d7725ac" + "54c271672721af2a4856d8643c7f472105f7b25c404f9ba2a441fbf8cbc4f2d0547d164c663b5e5f7b16259c711212d7c2158200d51eb719" + "352636cb4844c301c6f14f95d9a8906104a980dd0ef4a4ddc5e2abda6185bbeac2261294e046f7bafae165e0dbe65cd9235cd3760c78a147" + "0eaae69710981b8afc4941f8444faa459e736d89e96c7d686ce56ed03e89a097f317c2ae793f8234f7c1d5670697859c2c22f1dc652fe369" + "8bb45c86023c78190427ea96d00a601150b41d16ed02fdb97644e4019c352159fd4d6c42a2e33c4d9c368aa1414b597d2239cb74674de5d7" + "5e32e37d3e012dda4dbd31f04f144a7ec8b5547d94efa8d47527f39d9070ff5789d99d08959da1b90f94bed1174b76ab8d4658c4fcee307a" + "679993607df5c18cce02b1ce749ef51622a8e037c6173b80caa60ce664631094daf4a3a4ee6826b3e6d60a6fc3478813d2ddf12fafefacfa" + "2891dcfb0c0105759cb57f97c02caeb415227aecb9421b85c6c12735418f37a370f416a265197639ac0653095f43a0ac9e0bf29fa4aae50e" + "d4d5be57aceb0d684ca1e4160674a4330be11168eef9e6d8be6620694e2d9c4485328f05675c02fef95527df63214a31848f75ef8b96ea04" + "db77eeb9d06dc13d7066b444498d02f5daa8beb31c33f0f3193af1610194600527982e0d833491b04a87ca24e9fad38560d66fe1c7934711" + "88931676bdc17a09525a89d4faafeb86276c395c5a31ffc648ce1b0d0b172d01d5f2c003da53e7ce4773506fc9870a586c1d939a02aeb15f" + "b246e44a8e7f9ea9299fcbb61220d5957db2d1dadca8c95a42abb88e5081315514a5f5300edb51de0d6cbeaacee831c85cc47f3db65a1746" + "7867a059145dd92e23422017f4f8d79f702e9b6372613c968ad303e36732a44a6ea9308ef241debcbf5392eca71b167e5526bca2400a202b" + "2c63d6d497af01fe59ce18cc498ca7d83c84809fcc87152c95666c190476f1d5d31f32eee8509752bdf1629c1655b2ac9c6aa9bfd5b700b6" + "1bbfd2a560a6712d423143ed533b53a6f42033a681751939a0a4f5cfb170dc5a3f25974477e81d3a9b46e4c192f80d1dbf9281384d42c436" + "db9150ab76ddad58a0588545c97c9af3aff9b3ded4e393a8c1d20eeb6c10f47d85e22a708efa77e95996926e61a746daf517d15316237899" + "dcbad145ef44057e80043680043645000578b51c400040066c060a2d00010a2d0003145196440dc7f90df4439577801001fd5be800000101" + "080a7e069e553740bf29b669ec746dcd214a4efbe2f661b3e24ae00f2e049a61a70af5a280218beda8d935c681da89cf5b289f2f5f26e19a" + "67c33f03b2e92d11717c4045a1fb4eb0bda1d33c693b3b0b6e02f7c04d4e25e04c4bde98ec8c8c9ba5a96588169cbc4392eabc1d2774fdac" + "343f9eef267432aded780209644deed9034dc31de67dbb85278ffe26143847f5938aee38b95e34462339dda575e5d3f41a513a64b1db92ab" + "05f64624cfcdcbb9860b45a0205fe823080c08e356ebca1572d05a9dddcad42ce3cff44a9c30f54bb48d7cadf954d1e5f3b865ce2f80d7c4" + "bb944ab00fa52796b8ca20b1f7705983984eadbb217180eb1320b5b6907daa36c52df041fd899105e4e80ab0f14342083380c666830fcbd1" + "be96c4e45b3e97a574350985405aaab76aa60387b6ab55769d5577d0e7b9197361d356d2a0fd0ee8bc0e47c175a8a7a5d693ce2077f37396" + "2031dfe888feeb1fdecd4b9c102fef968f31bae23e8a875f37ac7190f39606f7081c7cb57eb1c1da914bbbfd17fcbb21a7c545389e02538a" + "88cff136edbd4230ab333269179df7dc9aaf1e58fd3d6fd0b147275816f3fc4f17bd8a24e5bca70e4fdb84fdfa08340de9fac62e8f60ab1d" + "157c64ec44252342f07d6fac74c9ecd2353b052907262adab67356bc907cd542bb82edd64667dd926f4cd7731ebe74d32fa7f5dc28222be1" + "5f5e228ddd9b362fef36e1a8e2b061b8b7392054f3d09c314e78b6092225b599f6e5582d669d2772317c1015dc980b01bd126b0366951f2d" + "5771d6752ffb359534c65e1e29a084fca38be8fe98d62ed173e57f5f4216f4bc7e3a87ac4d220d125acc6dccf074d162fee73d8318a1db7d" + "8f1483e2f5763f3f821fb1f1bbe8075975f4ca7d3b7b1f0226ec5cf928edb491a9a2f7b1272eeec67aefea080a85e47b9481fbe0648788b1" + "554fee8a2f2d6010bccede09f7d547523ec786a34a96c5742edd388b2a31867ce916a3605f2ef0dfe931a45dde384e7521dec6631197cc77" + "2f36ef88a8006504e5640d9521160bab97f133a8b0d64a7254e8e7aa96ab013aadc0d87c4293739af495064f938679961792302cb08944e1" + "153be15dacf495b4db8ce770f35ef7587c15f0593b580ef909bc65cc430a93bb1161c740fb284ead922f43ec86e187266778fee6c78482a4" + "e93185309461e1e7950154ba623d112e7e5f9985a5e259f46ef5b4d0515145e35b2d47d8de2b2e7a7507a28bffddda95e396c3418f3c18ca" + "35fc9e09db60222eba4bc736646919ce8b4e5f2d8be3c3cb0c435f93f9b26f4d4c68557814760b5959210c86794074a0b013caed322ff31b" + "5d920a23fd467ddf3aab09fde58e13066ec2e3e700feb19353a9d4ed59d02bdb55c1a65960199abd9604acd5757187dc4ecb6d6a1916825a" + "c36a452a1e7f67a6e558e40d8d3eb2ec22c51315e7253b1fae27947e8127ee03b849c5ee03a846f46988cb748470d4a48b1a470382f42bc3" + "5b0362862233a775d8f8ba716e2e5e20b6a35332e2c6c70df96f5359a050d4bde214c65c825a218720f840a3920e29de4f2fa2f47e3b5d45" + "a63b5e3a46c99fcfaff7e8a17b362874d9f10334f8eb0ae17a47f731ba0d251fb20afcc138233970f8bb470cb5accc4c14fd7a809c6c043a" + "6bb0c892d16cc0c12ac9410b627d911603e955eddcfb61483b6983980e400caceea156362aefa28b3aaf73089fa4a2451678d2aadfc27f98" + "e95816dd4eaf3267edef37a021ea037f277bd4a8a209cddbea616ff1d9217c098eeae145c56846519d6080a9ddf44cfbfea63114a5d8b2a9" + "48087291bc6e550b053c60baa7ff4775d2b1d79eb59e6775437c7ed2ad3e7b59a64d7d275ad52cf835f13d272a71ffbcac646b1f9ba40203" + "cf6c24161b942354647aed19c36a44057e80043780043745000578b51d400040066c050a2d00010a2d0003145196440dc7fe51f443957780" + "1801fde9cb00000101080a7e069e553740bf29d0903bcf10fef312552b5f2ba18cc8bd95f06b27a1050da328a01e6a3bc9366efadaadfe54" + "bdb446235f346e47114a890e99f43880754cf871a3243ac764541121e147e5d8d374a17b897fc12c591ae0a2bacd6f61ba3df6525a086404" + "04d6e4705adc09f3c79330619245baa95a2995870d1eecf71bf3167f98f9cc535e664dfa4d592dc8a394d5dd47ee3efba60132a01416a2d2" + "ec89613aaff587bd4a84eb55afbd6fd9aaa4a574318c4b2b3d896bea3277c7cc7a55ab40b468426aaf442feeec9a24ce34dd4cc287415431" + "91f32553c9cc38fed9f3c3ade6eac9e0c85d57d306718d491e802a34185e139c8623cb33cbfb0e6f29530517f644693904f2e5dc4bff5207" + "c927ef5ac8466737289d4d520225598f3fcdec49db5b5c85231b6dc8d8ad5f77dd4421714841240548aceb1ecfd7a69ecf61431359d35c56" + "62bacf6a5a4666dc49fbbd7d084b99914d93515837a4d4c369e16dce95aa1de43408de8d07c524a610362dd674a0b2e5c53dbf65c4334809" + "4a9d16b21bb4656200833f1cafa260bf299765b6a5f719459b7a694506b8a495599ddd02b89ac26ca0d480b388b9e4d3f82dbd5dfe0da5c3" + "90070f5cbb82349d265fec7324445493f9ba01cc18885f53dc24c9a9c29aa3b1630ee6bbb0f1d611cf5b6de0eb550a259e09c48962702f32" + "26fa1e2084bec1bfeef0c033259b5847ce51f355cd3685d1d67dcfeefba797e8a37e04bbd21e8cddae5842898fd91d005659f97872af03a1" + "daf963ab6e82a1b5f5360bc541693beb94e0bb3c1a3a8994fd615d17e1fad2143f81041ebfdbd281b6bf14501d199fbaab651cf23e677cef" + "89e042b5421a33973bb9d0b886052e48612c1459a743b1e45f055ff4a65a86ff263c6260a2effe8c47b0e4d40b07cb6eed915efb3b21eddc" + "530a1baef0647d41495e5bfdb9c543700d0425fd27f96fe1975ed87543d5856a55992c1ea3047df10b6df065a0e2c39e466154e1ed3ad1db" + "d56285f8aeda7901242999c59da9eaf80ca1d7cca0bf2eeef36e7ed111c6a46a6da46ab6847b9c5278ec801c426de417095790bed4678eb7" + "ff4344249ce5a3364ca32946a8a17a9776fd71975b91e13acf8f27850e0dfc782df50206f7cea1e6f9d637efeb95f5e37b1e1f46c8378e65" + "ee91d9a39a684ae3cf88d31fbdd83f48ae7a294647398a52a5d8e151b6343894af7e7a09cf4a32a6bd98adc6142262d299b40c9106e18e0f" + "e5a3d276d6fa337687376cdc12afda1144c90ce71d5741d30526841343b6adedbf5b4c19e9bf11e6f5f9bed2b6da1b32a678f844ba25fc0c" + "279320cf2a51f5c6d13b4d5c069b1175002b755a569fd150c27fc0ed1cd4e6a59ae256415a801971f03c2f687fa8334e1d24b3722be0a027" + "d4627a77b28a11296c6c23bbf3d5db9f04624d3782c45887263cc5478a2f77153d47d0eb4541d4ec2124d44909e4c416276e1970c4469567" + "247cc68b3b3093cffda619c56f3bf0598e3889187ba9c8ff63268de7fc45c6c31d74650a7af58d547a343a7e7cb9c7acb162260bb05c8202" + "296bc760c15ef48f8d2fbac536a3be3711841270ed1e5f8176571d8cbf57124f6705cce2846ec085f93484af36626a2f61f798d2f1437ff1" + "ad233890313233b9c834af67731c627df837f260f21d67b974568119e2510eb814db969f8b905a62c24a07b1c9752aef518c09de46ef2636" + "9869941b5469688918276d9686cbd60f04a3c6c3df17dccc9e7aae9dc3dafab21e4f0aa1afcb5a3e49ad710738a1287e1253c610a63520d3" + "0951f0e822ac899982c2c70afc45584e1f7b7e9d68d8dff0562799b37b8310cb3e51148b59cbc292508750d193c05ba8e400a429eb624d67" + "6ec6acb7d9a2ea1c08e13913aacb828db5c3f76b2ab92d44057e80043880043845000578b51e400040066c040a2d00010a2d000314519644" + "0dc80395f4439577801001fd873b00000101080a7e069e563740bf292dbe203d8f53f094dfc68355550b1f11402d76a3ada1f58bb9e3f321" + "27249ba3563b4218a802b63479346c63c1ac7eb1f4060d061154ff4d6011cb4bf1e88de1256e9e013b3732c70a1e98d176e6a4389f91eee4" + "afd6e1454aaf49689db0e5a05303d315df6d45473238c03ace96656748e3da6be979d88f731ba9574facc86e1aa1c2785e73b1f8c8f535b9" + "3bcd7ef83ef3347e21dd95c5eb5ff5eecab7805f2a40c18e6bf8ac4c59fb4beea4c913ead55b2c9136cf106781dac450775f5d99c12fe45e" + "76e6fb73dee9118b324cffb663a41168a38c9d6fc76326eda1d7d0b4483971cb86da593d3dc38f57c87901611596ee6e78ccd992a3cd9a69" + "b6330738015e9c4e655210fd59f217229ad25248f7d141366c13dd8ad3865493eec025889c759266ae86b06ab443b0e504016add5ee5096f" + "9fc575db6278cfae764de449e0b3a7c32bedcbe1ce936e02674178cfb2046956d124bae5e6f1681c314e66656bd6efe39009aa1868b8fb99" + "d97adc6d188cea51e6269b08ce65eaf37728d0c0d673537b557811036aaed6dc897c03c7e61008acbee5709a63f5221d9fc77e29eafbd0c4" + "e774a73cd4a4d045b15b2a0b0fc11ae858552fb2dc12164d85d26e7351d16696b198c8a8b6ee480d42923403c5ecc5a78819280c55ffe054" + "eac68df9a0d47e8245832eada447c2b45b82211743cc0134c9a055768f7784eff34b65d41634339e52f5276bbcf53c78e834808470b07552" + "b57eb74675cc378f55e822a72e24d44fb895913dbb333054fc62a07dff2824627e382e1bedba9857558d2b1590bbe05bd2a7ded769252662" + "c19149db70bc50bce8c69754648fc3aa853c3766b397d739c55ce8d0a9e11b6273685f3ed0eeaad7b10a85dc95e3bc970858a332c806454b" + "2911ded18b0021d4a7d003e4c32e32802615452c8cf19a4416403a3c91cf2facb429ab9a435d2c38321564f4f2487121857f0b86ca81997e" + "577042beb054b254882f961c8c0561ce554ddccc736c4771e7583c68423aee34664b8734223ebb1dd7dc1776262f99c4101323e0c16761f9" + "16951efae256a3abf7b6a1118d01a22ffe0236d8829266b171e181902d31afc3b1b9a4cb8d4a4378c47abba777541cec86694a862e330919" + "3ce69f502e7639d087d3241e3a244031519cf236fc8f7d67f0b4f31ac56594361d92af1c685561493b47b34edf54134489e4b3f3a0a27b3a" + "01afa4adf68efacf78c7beaf865d4a2e93b470bb78996dde7494d524cc8aac288627c83331d37ee1dcef7f79095e40f153ceddab746f57c6" + "069639e182f0e853862cc8e99affb814611e4820de8ec63e283d22ce8530b632e4f81646ba2d04edbcf4570c30c94855b6bfe4f3083598ca" + "255d6ad3f43e2fc3cae88fd1008542c4826b6c3d663d0679d04a9f82ad98143879cf4d490b6d539af84e65d60af3b4c2a9aea97007d64229" + "619627471e80d8053ddba4b902b7cb9c728d9cda71cff5f08dcfd5f8bbf200e382ec214f7645aaae2a0fc9242bae43a7ade4c54841df89aa" + "11ac36022f1a65d67b7ead52966f133b3a1f2adf9e2e03e4d0b79354a7b357529e583939f6216453991ff1a5b0f9989d7ecfc84a95d6473b" + "64ab34bf99d3036aec02b55e89d8f03fd47cc72f7092c276b3ae963ad777f4c9b54bd790d715554a9166af4be901bc126bb20bf3a5ea5843" + "d5733205333754dbd923f9e61e6c050443055c2387b76f12fb23342a9b51ede02f11c08465a564a639f2b271bb5b15c4cf2ca5f42e5283f6" + "aae798e106a8bb2fb06fd11ffc2bd49d3d6966a8e6ba68eaba4a63d2cbe6132b020aa4f66ae5676ca62ed4ee5adf00e9c30882ea2c849f60" + "e7e1f45e301a09663a25ea6ea54aabac87eb5d643209122427936e08d5beb4de44057e80043980043945000578b51f400040066c030a2d00" + "010a2d0003145196440dc808d9f4439577801801fddc6e00000101080a7e069e563740bf29929ec2f558974d56ad9ffabc9c298cf3a53ec1" + "3ad482bced1df977e5dbcfe03caa6d6a9df9b920cfbb99ad7b94949775089de9d0d465bf1276f185dcfaed8fbdaf1d8b185dc37941f808ac" + "10ad539dbe65b3dfc66209341a09400795aef32a18a3ad379e50925c7e95d24887dbf102b23a854f700aa97fe987a5bf802c29ebbd8ffebe" + "8e877f6c498560012bd49d274c1d88f9d3e9fc99259ecadc4c8649cfc9b5f9ba41aab69ef5fe02eca166284b7c52800d86d294aadb830be9" + "c68e17ab5a33700b913c6f2583eca229963ca22503ffb84f65a9ae4b371689fe053a48bab4dfc2d08ded64fe42a8ff167c67aded658d997c" + "d6e6cd612359b03e9640d4f6c5e677584fa5819f525e7d451b3f1eb86e6579eb0eb5c7ae0ae95091afe46ab8eaa66448fff0fbcb47f06557" + "38898019a0c2d4d13751cc751a8151351863cd9f8ada3bba626d81a21185642d5f29a25892180dd450968cbfb0e9da8091f1fb91376d1d57" + "85ed5fd2bb37980a464888475440abb0125da31d7944cd445130395c3c6d2e4bc4f06078e43e3e0f159860e08da0a17b10e317ac614a3a66" + "d23dd4504a0e1c5116d2828582bbcb8cf4e65b1fd517044bc4dcca56da14a82bba111c3175b69012ba5c54e4a97e2a0e6d56eaa273f7ac50" + "dfcfa37e3876001b32919f476c39076b07e2226c4b69e2a52df4089b6931c5dac0ec37b7ebf32bd6a635e46296a4c89b8696d69b5269cdfc" + "d17806f85482c987783485b7d8252d14957407803604315468a14642012f25b9c7a3ca5b84ed552671f496867ab02e06c74ec69376143b80" + "843afe236810de9dc103dd1d513b2d1e9ddf28ad19b1d11a39f5d2f929135303c44a523bd00ecd94e04e72229ce532b53174a3e0a09e81c5" + "ccb9466bba528c90e520115ad7833f524a6e157041a70924c661bc32945f066ba904ac12ee2d3fba17c3ccc56bbafdfcc09d7d78e22675d4" + "2f72bd2e3a8a029e5d203b5338e532f8d20a4aec761f8eda62a8c2f808a5532bd037b804d753f8b813d6c2c81e89de91fa2fd6fc0162b24c" + "eaa6a63ede95ea4bef89da034700022df24568466436791f4709eaba6fa0ca8118a9fc82b2e204745c7e3284ff2c30b879a5bff7cb86d3fe" + "3000b1a2bfc4ef85d6fd16a44f34bc09e4be46f75012e31bc0e07584df63ff2ddf090efec41f83e450153e5cbeeed9ce232892c88fd9ebe1" + "9e675e7ea4cf4c96089b6382afe7e0cb61879e17cd3d56986a92892cc16562e8cfa7ca865029e42d83b4c3229e3a249ac976ae2fb0b6eea2" + "65edc3ecdf89781db7e8f51c377c4f4ceddce7c552b3d9de0ec4865c436459568393e154af99847030df189eff0210f9048fca3acd2d01c5" + "03f5de0c045eca1221f33fce88dc4cbc81e6835596fce338cf680f37d8b070ce3a0729e4b7a4948f6516eb54587d423fe962f10a598042a8" + "47b32ed8a0159a82dcbac2de5ffb98f9b5474cff3562c37f4662f7b98e048520553e39fd308a4eae8085b36c3117d663de983c9c16bc16f8" + "ea86df1e87a19bee0e35c550a54a9d02c0e436aac234eb4cf18b8b96adfe1c11be88c87c58bfa6e4b041f1a2ae83cf8a7ddadaf8eb5946c3" + "c7600b87472e53eb8c5fb51e25af3594d7697236010d7d8418a8312285991b4d26daa77f0728c6efbac1785c052a1a5b106b54f3e058e280" + "89753c6c2f1a48ee5fcbe5e4ff5215a5b0836584334a909d979696869650bfb02d88d677102d0cbf17374b4092ebc7bd7dea966c35d278b6" + "1f1592d93dfdbdaee9a28384c2dd246ba3928ebc1315c6a4acd0148e02ff92eed414d57425bbcce81c82b5d361101229ab631bb868d4da4e" + "651671e9831e7da32df66a6717fe940e02a2d8692949447b7bc788a4250b978aecb2fc8eafd834efe944057e80043a80043a45000578b520" + "400040066c020a2d00010a2d0003145196440dc80e1df4439577801001fd54eb00000101080a7e069e563740bf29718574ebed5b64d8a8de" + "60435062eb1f78d629ea21823e0e1845519632edb478439c8eab87ed2282a48c52c5c69b235b910dae08925206c8c01953f42af09695d012" + "e0e504880b8f9cc26b1ffcdbf9ae695a049a21d7dd0f7b74323b7861eca23b4cc1fe039f3bd0037e61491ee7e4a3a634a30a608620b312e1" + "f7c9a92c3d1f8088f4c5d520a6ceffa2f6109bba34e559ccef55aa5724d6588a0ea9cf3335a2c93a3f85bc0a9df1b14906132dad7d47c627" + "10bba4e867799aa72cecc2139a4f7ffbc6768aa7526e4b994d16c772ba0d57c94b2361d3ac6474bfe2acd09274bb2dd76f297dba97d747f4" + "d3ec5af0a7f0b58af03ec04a9ce06f765ff0cde59941b78f7de22278aae2617286c7b366acbdd284600a3d8f2dfec9c64d730a2d7955ab9c" + "7ab7d052734b78b842f1ddf10a885f573e7fa072763f4691dddb3bdb3fbeda12c58fabd2b3d23adf9bc2023502567fd5eeae278067b4ce15" + "72e3b03debc6f33152ee04621e6b9be0411029b724c2ae4d75041f0973b946a3c13799549ba8999999a71b992eba1e0d783b0e7e14444e77" + "61f8d838405b26a85c7a52acfe264d8daafbf3db4c409035a2e17a54280659dd05495bc9862ce9a730e8540fb7bc9ec381e6753aee79afd8" + "0518468d1bd796a611972e2e03a1cd8017ec1f8d43fb486837570256406074768524b663505d87b5c8e0e103d006e19b68a39c57f5f49185" + "0975bdab2f76d553429a7f13e7d9f44656dc51238fc80b808e651c6cc36ad4f1853e6adf38b4a2eb4f9c71520e28de15192a057d0827e511" + "ac4bd44d7d5451a45905d2b6223e3c03c4ace8fb71dcb81ad88b5d55d1ed6f5ad6b362898baa5a18c861f780b5de15c3edf769160236c1ca" + "a09fa2e1f5fe2bd304d2d050580d9997995c185930dc8b580ea905bcb9630480eaee113ddce3b4d7230bb4a9379159bffb13fba111e84a70" + "136db3c36f5727381acb137c897635bf4cc1a75cd6639218848928d14b2141fb87da913f00ad96e29e3623bea306cfd100ecd2fa41e06602" + "568cc66c08f875b507d1f329203fed52242e9313a1a41cd856a980835aa228be4e9174f9a993bd7c17d4055929c1a19febaee324ffa11ac7" + "b5f5f5389833923df2e127a98814190c37880352b749f96961170f197a9448050ce469d4d123387861d153a1a5679627dd3a892d422cc358" + "a917e545240c62f629551b03b66eb46ea514c8063b4fb10e2ea13c0a4ee49674ac7bf76677537ae6e40f9cb999c26a94517747902c732238" + "ca92a53fe5ddc419faff02ba1d69ad18c1f84bf4da640e070cb7be2961ae5e903cff878a85011f4022941c15f686075fd002b3f7923b636a" + "8f063c69418719afd6e1e3f57c3b248e015b65fd098bc74e6c2597b303330b34a6a9af84ef4a6cffac039ef7c4436659d90e4a9d947c0e78" + "bbb3541ae6b2ace5b306a70f5820518c6d57d0f88db541677622daacafbb813698754a2b9aff432e8d69b03417f65ec99fd07527691ce09a" + "04c4cdc20bdddba95b2b0413bde8132fb2c86f51c80e765a1de80894ac917a256a43bfd9daa405b61704abefba8552a7958335124de264f8" + "7d6c33bc9ab0e58f956e17009fb65e651146cc26415045c3d5cb1ff0ecabafbcb3cbcace2bba521f87c8dfe7ab39518dd95c8fde5b6a678f" + "503eb7091a74d724f05ccfb3ca74dd29bbecb303f9ccbafaf9fd65803a412b24fa04274686c8fec46eeefbdfa887e04be810f9a45e69c498" + "fdcf0b0d38b5f3666737c40f45cfb9834fbdf35bc464489cac532ee2e2aa55976087fcb570b2651665e5ebd8f0256a8e5f79a104de47f18e" + "ab47de490db10babadaf7644d4a11655b6130519236baed6979648cf938cd986308f92cee99c84ff6a714245bf7a1c8c249a44057e80043b" + "80043b45000578b521400040066c010a2d00010a2d0003145196440dc81361f4439577801801fd36a200000101080a7e069e563740bf29d7" + "4ed4b9cb8f6eb78e6a5a2f600d62de02d87041ef90ea250d910ebe5292faa225bf8538fdc501c736e098bb59bde78080541448280357c03d" + "8c9b3225f6bb3c01638f947ee50ac24b4d8ba347b4eab29e0af0229c00c830f4021ef5c958902d39d9b6b92351da83f4819311d799e70049" + "f605bfa7846d9f0d72b0a604d267758941b42a6e01f53fbaa597917d08da474a1596176e2151f60967202ac1c66803d3b0d6222f94590082" + "21eee6e8deb0b3963e67462b2630599c726cbff75b94cbea43b6842d789cd8d5bdf655f2cab07a3f0a73cca5ea6791fbc615ae9b8c1be17d" + "59878494c36c8bdad06d169b7f5e25476ec1d48ea7e644dd5f461adda7e70172f5f880745ca86161e100da6aaa7df18c02b479f006e29e74" + "a4b08e1916af4cc7a4f31cf99f2c24cec6b5076442c434d9da48664cb84c4f4bbea9838f9c5a5010f89dc9f55773b93048d20266c0358270" + "9bf0a0fb7560dcb76b9429b0d1c5e714be86eb4f48070298615ce17798c72ba4bfab01562e06be2f22afee7663c911ba100b8ce91560f2f2" + "8fcc980751863d7c8c8adae78a4d28da74249da4ff36b37c75069a39073f3119bf152b9145114bc84792da79dab1d1094e83e299e7eee8bc" + "1f887c689f2ce1d6bbb4fe7ddc9ea2a4a6af97fd7e616b11802f930b080ba495084ac340f8fc993a709e858c6c7fc6d30a97f653c65494df" + "e6c3a92b7e9965d6b56ee834736c2bbbabf1ac7e9a1733598e58746840d7da4f2b8e482dd74e46876bf44d20744be5079555589714bd7156" + "c79789988a53bb68624a5ccde6e1a282c9524bd2fbe178da4fd6404201cf6d6f292ffdbed6c5068332600752c057eeb8572df9d986d57630" + "d0dcc6e3b128ee6834db3b7aea39b48b3638a78a7b83b341ce542f524b1e708b88028a2e3101bef6e195ca6efcb67e50c49e725a84478f8e" + "62309d0a662486ceb49c79ffe537357eceee0943f38e946bca80c568709589aa27bb5d94cb7ff97b2d71598dda626d03d395b4af9b5f25bc" + "6ecf7b0b1a7f93407ea07bd48a43109a065bb13b4d39f731fbe6f652da97ba27dacdef55b1561ea4328d119151f34bc5524adecdcd95e4ce" + "805c1a1ce61745dc1d19ed6d1f342cd2bcf2e0c697257ee919402990682535db6f630feef1e13adcd3618d32903698924752f037c27a7ce3" + "3b61e8aad81102f93d918a4e617018fb24975f2355d1ec330e66634f5d22a52093f01beb324aa9b3ae5a656339ad6bf6df46d51b1e9b0016" + "b9151de788ac452fde4d782726bd3ba79a4691e9157e298ff7965b42d710cdba23691bf71b0fdffec7e8fdda176e98fcb2340dde1b6dd5b2" + "f6a917bcdeb5f20027c419990031b6467500327a9c53c4c9649f7d7e848193623c925573394c58a4cf8833765d7606cb107ebabf00e1ff0c" + "cdee1fdba3d6836bde91644aa4536b8ef22cfef44271def7dda8f3ededf94ca0b9ed58c076f84afb8dd06e54e812266f6dbb27d089af91d3" + "391f57b3860d390e182fe19e3f5c4ffe58d3e1a27fe7ad8b3ad1c9275fe9f1cbc4ffb5944151cc85b66fe9e261689cd60c601cd6065ce851" + "b1651cd486a8def61f76cef33a8a0e94967a21c84a59cd9d4952836ef310ceac3b5247dcf3a3a009c227f5552055969c691636d7f7cd187e" + "4c7beec932ae6bc4edfde8077b9c2027ab8fdd29ec93e196a14069ced55929d851e04cbda17bf05a6446f7c3d8efa29115cc6269e5d037b8" + "33965159d93b70dba826383eac0eb0e63b8a7555318a8fe3c762121c4dc27cd40abfd45bda5fafb4bf6ccef802b53fbdb026ca46fd3d4e42" + "4307344e530d5800bf7d6d995d0c65cd8bbe6e03898b357e70353a2ed94e2d2eb9da6a0912c1023c5ee8bb1c0252cb7d363969ba637487b0" + "9fe2be44057e80043c80043c45000578b522400040066c000a2d00010a2d0003145196440dc818a5f4439577801001fd704000000101080a" + "7e069e563740bf2ad9f7a221c7a23a4a0fc879ee05e60d34923071cdf2adb5878769992dd41fa3e38fb55c566817c7cb40dbf323895c58d8" + "6eb30b826895add563462db1df28a9a3e6440a1453a6dd23b7553f17248cdf9f55a5a871fe5aa0c840a3d360092cbbb4b229af5d1f87c37f" + "ed1f63210fefe0e108e936335bfab484b8cba7b53c8e1768713b6f9058a724d34889fca6d66f9ba0da9ea3c2bf896fd3f70d03798b1f8c3d" + "95af94d25ab56beadae7fb46a2ed94f83b84bf6c2dd6d07ba8bf469df23ef8a264a1cc11ee0cc39c3395f0765f0dc3019bee13482c4caf09" + "b7990d16a0740710fa8032d2ef163bc4544f207c54e53e7ce95c5aff5171633b14c38caab7602302413bbd15d2afab2bfd1cc2d3dea68b47" + "eb9230d40376ce444e3daccdf2d50cb2952f3068ee64b8d61a239dfb6a47db466237ed62fc1c45fe425abcdc8effacea50d861cb82e80568" + "8a6723c2cfc3cb7499cabaa4edf9a13e52c2e8e220d5c7e49fa8744d476cc9c1b648efa09db81ebb6b3b9e4a76eb82c9599bc7c78b939697" + "f4dc3c42612650b97f8cde18cec328c7e46db1fdb0e8e777daecb6704f28ef44b8c2e89d6bdc19dfefb1c3041b1b00c7f969f88374278072" + "d8d87c3d930ce4065534df86c672807bf300e3ee9125fcb9ab90028fa72f3e548b202cbd93001516eb7eea36e302942e0faea9619124a5a0" + "6b2c56b33a21a8d5ded781dbaacaa6dd769432fe96837e4e7e12233554a3a7443972823947e7a68b2231b276192248fb78afd6d0a5cd0b62" + "d0da466b84bb92c404c98a3fff6e043f11dce64d3d5c1f4a4cb4f47fc0b72a88292254820b5e1eecac7eb96500e584673659f93ae1876caf" + "b16e98c1557175594737383a725d4b27daeba18dd967d7537e3fde2ba7bb5b8a3ba5d9288745a3ae79ad074cc6c76d9c5909464289086eea" + "6d925676d7ec191122410828b532a18ee68320d7043dc28455cef59e3ab17567931fad6072857f28652052c69110e14718d360b85ec46a1a" + "3d4aada29669bac8efdb43001e10f155da0597fa2a9701c047ce9c14fe1d5351348b3c5f6fc6603b3727791812d65b546105e8c22f1748e2" + "872b16a15faca3fde0b0d7c144382fddf7185ebf1e89695b2abee966ae6d95189310e2c719fed5c25f1851ca1a5c9d739a1bd52a443dad6c" + "d746ab11ef77af4ac38146152bf0cdf85f6f44839467ec69da76a34a7d47dcd21c8bfe041b33f344c0bfa7d36b247054c665a79cc33f5268" + "56a91a058583be6957b5d4b72d1e4d12458cadcf1d0c80289fa5929201fb15e07ae0e96aa8533ef44ccbebee5f160e2f51245d8099b08ede" + "6cab162b2badb2b4534209a2feb4ad921ba4c956b11435c8b1d07cca4281b32cfe31c2f5947e1ab60420a219cafdf66014197ea8c5926e37" + "248cd0b20e5e486066ad92af0451240718e4c4ed6be9cc4337195d81c55983fee698fbf24314e8cc8fcd45a028c8c88cb0f43633a3c5cbfe" + "598fe277159a5508bc1040718c43c262522f94f83c710a0ee6a702793c320ffacc5afd09d6fb88f66fae4d0bf2596fdfb41ab30dafcfcad6" + "8b559a85bcc0a07927b333b3990582baa7435245800c5312e9e77051fbd1c1db6cefd72405924915667c208e8f8ba52f227cb4ceb9a09199" + "ba274f05bf8d5d84f9c6303411b4261ffbc59d07f2781ba1f5b831b01caa9a955f5699436532bcddaf1d4bd947083e7367d64d5c66bc7648" + "d6f8c18a07f87c2ccdefa9b61f7255a21cea0f85a0cf67f51995fe2338722604325ae2ab7cb891534cfbd8ad139fc8b2b38c1e04cef91214" + "0905c0e7cdac8ec49de78e617cceed19672c16ccf1e7de4bdb0850b35d2f73ee28200b7a0e5bcd8fda3d0531c5300743722968a31b02d474" + "294851d1021e670f7fc4c21a44057e80043d80043d45000578b523400040066bff0a2d00010a2d0003145196440dc81de9f4439577801801" + "fd125b00000101080a7e069e563740bf2a61eeed759c9d502cad61a76b9842ad6f4aa1013def63f72b4ee67fa6e999152d220f0481fb265f" + "cd8d1d20e9c8ec63c4ee6fd1d03ccd73bb573304ee41fe3f4c3694d892866538a89922b5ef07f68734c3e5e7fae1adccd6e21894abbe74be" + "af7de77b12957df7c74288403f928696e276e79b6d22febc3e42b24fc24c3c17d8c7135d5bb06eccae35a6881cb85b1b9522d4cca06823f9" + "9bcf8ba48fa421bb71eba7bbae5f8252fde23b90894af2d999a7dca58bf9fddf3c54f698532f6c05efc7da4cba03f2095a4ea03113a56a75" + "cc851af1b8bfb64ec114c38ab547ff04c264cbfdd6181b41f694d771a48b2e689c37324989a54b337ae74db0083559921e26d58b2e16845e" + "7cbee395813ce58c6a8b10e6b47be70b2918da7604422949c04317cc33ad08a90dd8898b647753075a1851eb5f57f9b6b96580fb61e85fc8" + "a5f1e6a1f44ffcabf0fe816ccd208fbd0845f72d004898248f9969141dab23af2427fabf0161563595b054538277ebc138d9b9e8aadf74b6" + "bd1601df92e28ab2ebb3ff4a57e614ff8a16accbdb6edf87a5d2bf87d963e1d9c3be05c3a63676db8b986bb228b516aa095624814fd4b50e" + "24147571a317564fb465c6560c1c0155bbbc4304df56a7c8532ad3e96a397ea87fe66f2115d053e30a41b82c09282c5af4d0ff543ccdaa2c" + "c25c1359e6c40a21b6409e33870d5ab6aa15d43491c87f65b5298db0764d3a4fb095a54ff5691478c602bfb541ffc8778c0353fe48f5d4f0" + "20236c3f2200ef53e879fbbc6d09ae6b9775ac6ddd5017edd955ed4d57f88d2f0e24ff9d04dee9b83bf26b2b204de167ed8f8c922609b8db" + "5557b77981f106dfc997bb994efcd2e12f07a99c49d66641566c3acb0bdda02c6bda285eaf4bae694e9fd6d2b19144d2308186cd71fbdd8f" + "6beae66f831bea7919ad3bc443fde441ab0c704a41a4ff372aa93bd8655ef81bddb0bed7a802fa2c2d38965b047cced233a42a78198b61df" + "02b9020211cdba4acc46cb86308555c5e518fd356253057512c4da6d3df2e74c9416502d44cdefed408b63daa86130f946d00ebed9e2c5cb" + "65b635bbcf4e97dfe7c1260af18f61ffe9f266e72a672230d5326f62d943fc1fe8e5c7d32591e2efbcd7890e1e0f9ef1d2f57e4d476d2a6c" + "4a7126a3e5e340d62f0d0f75687c8ef364b22acd5e3fd3234fcfcbf62083bf760116fef01e413d6077a472c143abb75fbd994587d5016f97" + "71e54847c1fceb2b24cca5360518e89ef7d10ff04c45a4decb69954b7b16062c4b65c46def639d8ed0864e6490a16f4d425a0d4077d578d2" + "964f034588c1d8bfec2cfa20cd96dab50e881343ec6402cc68b7a561a2e6ce12c25afd3642a4d294691d909cc556d9d5f1c7ae170348a68e" + "d85efe4bdc1091da27a2180aa92d205a58e0dce800fa0ee38ac7ab546dd6f00791787aba2226798d1113d4abdce01404428c05095811e57a" + "f1fe575c56461cd05805847b25ec792335832f1edf674cadd3f598fd2226e68f12e870934cfc11d7c9e6d8216f55d575e0bda5b64ccb615a" + "0af3c398db042ad3ffc409c550c5f3433f9f36f2c43ae69497bb5c3ad69b5d738fdca25867881cf2716bc0459a7d3cdfa0f8de66682fe87b" + "a0e3976cd759a23a8bebb473868988ba6872e9e44a77b4494f5fceae2c3c3bb2388a42eea2471e5753abefa22216f56f58ea99d602c610f7" + "f504e6caeb035f87335786d559f76ca7e36c97f8060b9d65c91648d0d2b9fb7496e2da9a95c96e986b3c3f024f73a7101ad5617ac93dd165" + "bd49f941e1032e74c095eb8706a4c3a52def56172fc87b07a7dbe012cefb6dd3c738c50980bb50f2fddbc0dc2dd99f47bdcffa43a3187d30" + "4ccb6c9b1912a1f8dc66d7ee15c8b6007941553d6744057e80043e80043e45000578b524400040066bfe0a2d00010a2d0003145196440dc8" + "232df4439577801001fd2f4c00000101080a7e069e563740bf2a383742182b65b90dabf63e50d655f769ad8729a758555d93be9bcbbed300" + "f05b1e74fabc541ccee22b93c53dd4834f0b8072d27d1cade75922d65ee18b00e28f3860d61f69e7c7411fabb47abc216b9a148fb2920e11" + "dda898c11b58236c74b8c03dc56931ff0a9f2992f607a81566479142b45f0be1c8c0e01337165b9ff9ea7d19aa6489d8095a824966275f03" + "061390e4575a173aee2fc0effa353b94ffe7961fb20d6d0c5bfb8e314a99f08b1af9d25ead1c0c34cde9884af498eba94ded1eeb079e6121" + "86fcd6d76283073d30d49127ff400d8279037b90d8d800e19e064ccbad6fe7138fc22a77ead7d631f46248d44ff5237c7e4a26bab6471f9f" + "02683d83d57dbaabcf8f6ac237842710bcdf186557aae87f5f74d178fda9bd6e8ddf01a1ae6ea723199c578a9e55aca2f119cf4e4e9fa0da" + "b4d08d999929a20dd8322fa3066d6f4e21f25510a457bd3029175580453f635699ebc6818edc171cab2a2e1f69cb5946b2841269a945a89a" + "0a19d25a687aeb45d65a070c5fe4b204307eb40f7210e0ff915d41956d6c2dcf766ac982933f85c242687968062b51edbb28f71d9b010b14" + "a71097dd976cb8f64bc76e642d7208e621db391c467c9ea1606b24a94e056fb73a7dda67a277cd21d0e82622bc5bcea9dbcd64f03f2ebeb0" + "a01d0ba97cb6b4323a9ae9f7c04fe72c59c152a1aad503fff3c6e6f4a07a74f8c705ababc87a635b9dc5718ff25b9ecf2716efc50f8a42b4" + "d0debccc4550cae711c979b7ff7733583cf4aa863a2ddf7b1df5a66da8977d843d5543db8f14da427c3bb24adf6fc4864a16373fbc3819b0" + "15ae27159deede377ec8a44d53df78c4c030bba77f496f96022d4f9f76c6efec9af8400623eb9dea142ce7bef7f624ebebd86e1a87f879c5" + "12dfbd9a10fda28324dbb545e65e9df45c8cf5bd5ae013b9a58bdf8817b2eb0dcbd3e9cf27c8c1496c45ef0df35934002e03a494a519324f" + "cc62860380aca620e2976f2117ad655dbfb1cda7832d5dfce7e8f4275efae351660a6cb89fdd236127c301bc74bd32319643cbe67ed30d49" + "679c239b98002deb07dcac5697bc2969ec582061d8d168fbb834f024835923af0a9dd341210a253c7efe9d24c0b2de6b5f39b86e2016e969" + "448ab7b9f9cfa1065d66686f116ad5e22f2be912eef22d18f39f356d0fe881e4b0dcb59dd4d807083cfb6c655af3025b0daad3aa80b2835d" + "c0fcdcb4db3aef1384f52ec6433df255779665dd5dcdb7b5264dca783e38819ab875fbc185cf9e1c5c7e96c3fd72be24136df52e005f34ed" + "c251e430a3870929308b108520ab9f4395c281280a32ac4f03247c2bb94bcd16263d509d950953f109caa25998456ebde28275c26572ad53" + "879c62f592bee8ab97ee36660b190348dd1e7e851275e3d9e72e0ad23e0a9777f05ff97d74e677044a551b8e944e2167ab74469b8ef4c2ce" + "5b08b048c8ed8aa8d0117145c7634fc2f8987b20dcd5ef523556d7ea9b3c144dc55d89beb11b5a79067826cb47c8c1d35571ade600bf3559" + "3d21b82ac89dfe7981cb989d8849077b50217d85e28e6c635ae988dae76eed790b8d0a1a257214617118d73ebe7459f3ea86e4da8cb664e7" + "e60914c3e77f6744f2e29a9cbe3ab8f41fab89ec93095adf39301e8d64008e276727056a45724d370f0b38a504398f88ccd89bf13f5063cb" + "e8a6a891d58585f218ee5af66bb0935f3d822227bc1122b9aa2ca10e179db77dd33a2aa361d7fcb3f34c599e13e2ff70750e09d39996c624" + "9c18a8827bbc41acc78b6652120c1d70f01a545ac142e4cbe5d93ac10e53f0ce0352b21f3bfaecaccc86c3977bdd8d7157f65556fa449242" + "b9028c9fefc94ee338f02eb968f9e2b889768bc55c61ce2979c1807662e244057ec0043f80043f45000578b525400040066bfd0a2d00010a" + "2d0003145196440dc82871f4439577801801fdd52300000101080a7e069e563740bf2abc8eee097f88ee74d36f77716debd7b830af21f039" + "e2ba9bc4cf7aee7f412f1f8b3d1de32ab217f12665981578611d9deb5b620d374d1633bf82ac88fcf68219ab6879ccc32658208d400ea188" + "089cabf17d8e7c3cdc9de0a1d47f86ab0abca36468ab5f782b4fc35f520cba4e9598d76d20b11f5bbcb3d4a894784dd547136587d4444b14" + "d0292a996e4b69084ebdbe3fab2c20498639f43a1664d5b381ed9f5536b04f700629be4e017a2ae2982f7a5fecc43cb2d3192b694d33e15a" + "a83ea4aa33746474a13a19e80bc88944fe2f6bfb164d777ec9b25e03835d50999cbc22bf4fa0b0f4c5a3a68228dcd77fce86d19ba815cd0c" + "9d8d96fd85d88fe50b6ebf6c5ae25ccddb809261f9fb60b42370f706f5a8c9a65667239c3971afa2e8de418fa75c779d7de493bb9b20d7bb" + "dbdeb89c043854968c150c28067e7d3de845008456c69fb9eeae146b37ad8f38e27ae896c0a461f36148fcacf4a61c51edd5cd4bf31f64ee" + "07928d96264ca23b88ae1a8aa12d2cc91f90c66fe2a2ec0ccf7fd973e4c5cdb19e12bc464dad6a794b95b39273a22cc97dff073cc9707671" + "8672ae1b71482fd37a4eccc27c6c1b76cbbefe6d5d375516a66cb511da4f259f96fe9e4ecb2aa2d1c6d879f5b03f68d2c66610db886891ff" + "06af9371fc3d777da820f30727b2ca16bbbcba5f0beb8b7f2fb4f6a95018310cfdc0f9b263f32df1741110f36bfbf2f0ac523f60ffd07bb5" + "b4155ea7c33aa0f2115751800ee564f798cd7be6a8c873772e5fbe6be502e19e5a02beb96c8385e1752c166b78ce9eb85ff4dad61edd386c" + "938c21975c046ea7e5cd10d6d87be08113bf3a4ab71b3e827e700dc9e23b1c3e8d93005582fcadb3ac9aede42da2b4909c27d1a836b24f18" + "3f3d0d521feda4190b5a852d019405ad26891fb29ec8b3652e8d434663ad9568ed1e8e8ee5ad96411191af435328a7eb52055c3e07c48fa7" + "90ffeb10c165ee8272a41ab6b95b243381fb0ecaba9b099082d964ce7814c8d43861f05710e3353e0d144e9b524ce9c6026e880e27212d79" + "b563e5068a7b49d30930cfa1805d4494f0970c70960eabf2a9f87784a578ec2b387c7099003c45b1c1c9faf54cd543c2e0e287d8f258f391" + "41aa7369ad28f2d27694dd53fe72cfbea38b5dc075d7a4e427d26460cd5e892ff08abeed5dabb99559c14c6214aca07cb218a537c2991e4c" + "fa2c57ae800eabccc5d6ddf2112e1ae7895fda2816dccf3ed0e3932d443cac851b923bec4c0badaf7fb1338a818fbd6e6843ebaf34073603" + "8b9bd6888c897ecefb14f9ddb71f57d2df4cd29a180cebe3ea0e25c2a9910968f32ba2994ec1d5a7b8d426686c6f0a5d2b150543f78e6488" + "2d5eb52affe50eaa58cee4ff8bef3fa7df8e3cbbb46e01c802d85caf1be1687555109f2f340873ad304639fd14ddce7845e4a48899287782" + "32c8425b445fdd5de1f435b2be356841624232628f64f1a5344642bc8c4a0c3cf06c70717bfb0b4098f5d4dce8b1945f21226d1d80f0ccbc" + "bb33534223ee68298d4bc4cf840d88c6f34b66f111adc3a7bd93d05a339d72c6368df20b9043e6e48f897b9306e3bc45e322047cbada3244" + "ff9dc4f179a470ab9bc7718d0227dcd7c1c49e0a842b07ee0f12cf94e3eee66232fe6c3f36f6f08fd50d7608bf5155abe4a15963d22fb6eb" + "7c1502ebe1a6efa74068aee711e454e88c96a8f861c76cbddbd3f88c0e3beab7f08fdb8e68ff30bccbcb1de91b6facb872db3d85d49bcd67" + "57fc729fb750669b3ce96f5d00a717884eb53f6fe0ecd152ebc1559765f4131526d4ddfcf5447341809acbecfc20c879f5e5762eebea7318" + "2c6c8f3b067b472e0f8dbb11acd14ec0bad2929eb6df0d87896289823b4edabcb3c23bf9b7d2c044057e80044080044045000578b5264000" + "40066bfc0a2d00010a2d0003145196440dc82db5f4439577801001fd261e00000101080a7e069e563740bf2af8d8bb5c75e5d9f6e1c5af03" + "4bb3f7f0ce8cc0e53d7aab489854a02f849e286520dcf9371d9c9f6a43d5f606cb7eff2abc7f9fed697028845a888be0491fe33cf3a3f63d" + "0bba4e880f8537c769e2b9c6a2724d213d43a680189a7865cddfbd0e09c5b2fd451e4915bf4ca30970daf3bc657d6148f8ca0c415c93b41e" + "686edd7ae663bec135ab499bd23fffed97b87e019fb946724127664d2ac4aa87b084730778c3020866f3908c9cbcbef681c97425cc37b30f" + "7056bdcd401925eecf8a1db0b1943add96b56827d9b8f3197765743ab06e449731b97742ac7f13e54917413f8a9e04cfbbaad9e8b3dfca50" + "2d698558f58359d86a1a8e619df10342861d15e459cc89be3b20c806907f64e16a1115d592eed6532870184d6339b37ead8ea5475c4aee25" + "d8560de2ffaf2f4c71b02b67cbd0c676761d980a08a4997466b95f851e1866b5f890bb678d33268f5603cc2183cbb55cd7c909b641eb42f1" + "3a7469bd6d09192463a0077415c4ecf9cd034518b31c8693b2373dadba9608e8018a957d4fe823cec7014c2603d77274599d1d7c21f725c9" + "bc24c6642ec9238a6020dc9fe3c4ff339e2e6b61222f2097bcc78e339e0e5b58654538756c6111556ba4a324b9fee69056d9f7c663f3b6e1" + "cfae57e69cf6d4af45af1a489934bc7dfd8a904f1d017888f61e5787319b5e46ffb28f4784aba7aa483be29aba720414c1bd7461b2efe0fd" + "534631d7350bb364d6388ea5c3891c78d4b7c78d6a70830a62f84c489f1d8e0eb94a967d8f50dab8a5002e89a8178c9a0f24ebd848b41837" + "c66ba9ef6405e087ea2d890ab1373707aecf109900f7638dbc0cf48ed378179e39e048ce17c49011ee2a83f7dfd7714d05acb1f9a9449b5b" + "412e1d9a624e8d1f246f2f241c5595eba3feffe9f8fa3f5aedbdd425ce29bab8e7688feb8f9dabb354a179ac405b9c5409792f8f1bd45be6" + "cebec267ec3de8d4550123587accba0a3e4afe654f3cf0f4ff460719f4fb40f36654f5e225bf4039b3523019997ec82c9ebcbb6110c8edab" + "ed8b4ba076ffdf5e41bd4bacff701a5a16eff3fadc82b6b03b1cb3718045c92dc306cee78d2fb2a99e3c5ebcf0ac6910a9e821d7c26b15e3" + "5c520bb71a0aa07e0d7889f77e78efa7086f604479c540be098f458fb32a05c56372118fb7a989266570fa7de2a1df2b715f9c41c36ab639" + "4d0f3426fc6a0ab9436f2d4860ebba5894153c1d963ae3bab0fbc10a61ee6f05c309593cff2b7da1a3b3433475b084f6f4163d456024d742" + "4f3e1219e3f23da4b82fb3ed799a5056198f957548a36b1bc382fd2c1332694cbef2df1aa50142dc961b6895258fcacf32cc22c20c31632d" + "3ca400d4977624f7641385bde0d69c5303d95a58af76da8ee49875490091bacc5b017560982367907be12fd7ce0dfe1bc7c2af86fcbd872a" + "14c4f3a4647df6523d31850b96b7d575ab94663ce7fd5311d2297d3f0fed50a66c26a5cc2e2d28d630ac0020985b3d669aeee4d954ee419c" + "547a738f0f8fadd1fc12eee4a8210d96a635c8860d06ed995d3ac6dce96c872fa298bb24006502ee2aff91e9303d040b63e6d51b4100b8cc" + "47c9dd9084beedf9d3b624d8e085bca2002f51faadd85de96b9eac0d62dc514cd3fd5ee9e046400bd40c7cd28f30ac9a1513069d69da6c52" + "bd9949f7846e25dd66ddf0533ad3d9dbdd32bd47ac58523e0015ff98a810402dd00927857f820336c3b1dc1a9ac4a558134b65ad3da1b551" + "010215e7a4a9b79d59c774dad3171527568d0c76bcd229251a541e89fa3b24a7a1b0677a63e9f8e864ba4da5694ab3a4a463bf02bdc66b28" + "a2b62162bc418c7d8037c326b50b12908066b1dd19293982795c7da3da0bdb8238ea18d7e2168bba8165cff8c424a61644057e8004418004" + "4145000578b527400040066bfb0a2d00010a2d0003145196440dc832f9f4439577801801fda02900000101080a7e069e563740bf2a17aca5" + "49d8dbab7599d78df435bad80ceb8df61524054df2d159aa1c3f023d13b8c7e3ac57d1bfc0f1f374b8d99bb5d5af44bf3172e503077eff73" + "d5bcba48f65e8e12117a26331fe1ad98a0deadeb43398f64fa03657f13f4a8a6c78e0dc495991fdbf4e6139bc8a6a4f5d09c4aae527d2024" + "be258ef9cbafd0679667bbc211b3a72dd117779023522f74b8e3973cad0cd0c1084bf0f51de6ccd7599e644e9ffa8e061dda98a369463835" + "a31c3c9df1c7d35eb5dc45d58fccc621f475a082d457f7f213d40a963ee0fee3fa8e24371f229dac77872e0e2e2f66123b24062af39ad61f" + "9fe412c1170d8fac7cfb2e6c2aba4ac536f459ab2f21ea8bf0ec60a0d4c328ebd89ad720f0f1ac9e362602672798828fa8196d4ea7478acb" + "dd83a97c7c037a102ac060afdb659280b9f32e4d18df5c438c0cc1c0befc495068f373d3286ed08890af10e2f337c54d6891bf4e336133c4" + "f846440af81e4bb5ad307ecfcb624697c4b4a6592db3316404312d0264902be2d6fba7e7517ee74f13674a5ebf953eed625e1ef7a1dbfa7c" + "6deb0bdd6b9949fb2598709f5ea0b5cb766204f1cfae980cd124731b8ded85fb76961266316750808ba220dd6bec9aa7c9e1c0d21a2f4f71" + "92e798255d248e0dfc6eb776180f0333de7f964701b644da5bb9459b2ddaeb26487e35402e0594774cd228e782e1160410be7449826013e9" + "b53fec9627468d296546b07e3b6969e4489c04dd470993ebd928cf362577b20253a0b3b996ab7ea89efbde89447f46c6d47b7aa8db17549c" + "2eef3e1e256d972f828d2fd8801fa8659944da4dd2cc26254390acb8a52ef9abf0254b83fb6da04a91dd1a8fe7260dba4bce58b12374e7d9" + "8da9191f2fd106a57a298d8e8e26c5e20904d7f59ad8fa227439a4a78feafa9741fe47d0e84bcd1284db2fc73c0b249307d13aacceb63ba7" + "7dda62fb5764acbd12f369e68a3c1573ff9990760ef6892cb17962ad771dab99d4ed256325669b5888e5400ff1a5420004a94e29a77d4895" + "a33b5b515971eb55d07b9aa829312b7d7a99dbac61b6bc2d32233170d0bb1dc0dd3686fcd8e8055d96b6a2927a08e06911b4aa0beb242308" + "5024629cb5eb9f4f37cee8046d524a5d27ef255cf7b41aa9f47567ae2d12e922d3743028438063f14b379e7d2aa47bb6795e068ce1f3c235" + "327bc26d5127a185ef195c073841bcd38e63b71310c5b33e1b7c02c08e14c249d4079cb4903f9a344dd9be25f4a5030934fec7644e294ecb" + "3840414cb168580db4d2842936144e54f4434a7abc82d5e25bb19ee14ca62706b49179612c561389bb3c019a9e0c106a0404b4b87e85e6c8" + "4f619fcac333c2a953bceea82a5de492c768d8881b1442df3295a4005e73fd767cf262cb28248e53519a178f0f5cf685c48a3907ee4d6779" + "d767dfa78f009376aa2c3f64d614f97b798fc6651baa1b634598575d2fae3bfd7c148c4ee97aaac63e74d676c597b89d7f88f9cb57f0028a" + "25253f303da6225395d13c01ccf551d787b775f00edf6c77f154f0b823584057416690ccd9f717538c25bf50c096ff8683d57a81abff8558" + "73a1974c20dab6e904f21ae0c01263a1e0d56b32a73b44ec1a5ffabde0ddb499762e6f1769982c1eff86ef2461c92f0c3ec3c8e32c2f1287" + "3633ac539b8df915b8fdcc3aaa1e00cbd84eab46aa78adb4431ab4201736e6375fd95a03670528a3d7baffb3d2bfc53e85f00f44ac5cab48" + "d9f1e820fd4044e44803030b9f08281270c618cac17d84b186b24446bf77fe3bf33935af641e40489b08da19d96affb271387b7cb331b3d7" + "79d5532e0610b0385137418952d8337b39da9065b4f3332cd5df10a5b867364898f4e67813142054ead3a436c4a40a62219c960c79682cde" + "1f44057e80044280044245000578b528400040066bfa0a2d00010a2d0003145196440dc8383df4439577801001fdae4c00000101080a7e06" + "9e563740bf2ac2d8a713e6caf003ce3421f81a4f705ef9245877213d6524cf121a548b2ca819a0fb2d5796546525e9c93129d616020d535a" + "2a79ae034852757fb019461c12b95e0d13340fef3f1e893ae2ba471b931d6cb3b74635bb2167e3e3391c069df988379f76026808b78a880c" + "b9a9e79651bfd08b9b885404e3247a98106705257d6227ec9b91da489515b605e95a5177c05111661d28c2881b1ca2a1d242a2ed60093b18" + "c38605ed0b1909bb747a50f07f5e60b6843e27c0892b2aa2332c450479e2098f95dba88fcb0bd075b81c16c475b6213e7ce8d23afb063e7b" + "482776ceba1e88fecbab826762d36ec746d34ce6141fd297f722e34e13f0419bbb23d7549dbd1dd9ba79124a24f7239784841bab69cebdb2" + "a0f9d7e66f6c3a05048a0ff94e2e0c0efd516e8698a1bbb8c922054a1c3098ddb890fae90ec320da0fecb4a2c9086b72a445eca1b71b0aa0" + "b4655168edd4d3d6b91ae02fd1ab9ef4fc4fb51c28185d6373cf090b738bb530d081c3eaff08916754f96c0c0a39ec60c5ab7c31d8549fef" + "6f6e4fdb08fcacc36df44a76b001c10c28d6f52ebe6d577f571028098dd2089a111d38ed654e243e52a292bdd5d57985804d9a3fd08412d2" + "918d97b3299e34ad2c9cf57b3145f19529dd87a79165fa732f87ad2bbebc8c41d6e4e591d42c350407d2327b1a202a682d03bb0fa6c421d1" + "e355d958a158099a8d02f514ab71b7d2b1613596c3f9e96c98fd258a9177872ccb847c61ef0df5eef69e84da2fedadf36239f570d855474e" + "c99bd9a651f3c74bb7d626ca52d57630eb3bc352ad98b300636c9d911e1fd49c15c178ca222ceba96f86a4b68f5ef65855c87f1c90061f2c" + "fae3c74ed699e1cd1a55fe4dff9e07ce1c285e1593f6e180991e1d37966eb0f84ae454e3915de5a81c5b9fcd4f88da53f1afed9827d1c5c0" + "5052defa563adc7e6cf117c28e04a263a7f387a5755ddeae2af02169311e69c5bb24061c40b1801bdf75c233f2822c3a5a47b34880352322" + "6fdc4bfbf14eb621069db5f5bca88f8bbff16bc67b9921942e948ecf7644c3efde66abd9aa420163e21337c7dcf8d0e67bc757003e20ce63" + "ae6bf92087372a58ff9161de96587564b0aca9e8da5f1c207f26320bd1eaf2665789657d08b675b80856ee122461b020d8a8fb871a4cb719" + "d80db0ede0b58ada7e5b3a98ea0aa370fbfbb42617e394373fec9f2f6e8e1a0e991a8b7aa03b9b36252fe44382398ab9a5642e6b52fd91a1" + "58231aa4ab7a06e3eaad2d8d171161c8c7dac00535c3adbc74ff222e29dc6b93054a0a94603545b2f001ce7f962f20190bbdeeebea861db7" + "1334ab5634314647ebda9e077e2ed79138dc58d7a45c6a6f3e507216d63146f739f50167ada612487aa709ceeeb04873fd3e16f392bb6f67" + "9b8bf46db2e651ec07d870f60fe563a083eb5d00405225af9eef1ec879c1e0f7d1a8296724db3cb2fc7185c56517637d30e42285cf9e4066" + "62cdd05a99dac47baf060813649a3e90a61c8e37cd0a43c92b63daa0835798dcf17237a6e5d53f13e7de66b520457af3bcfeebe6a53aa1d4" + "e04362df88f6376b826adce4a2398d2333325325f0a54fdaa65e0bf02423afce6aca2dfb073d41f083d9a0ccc250dd4b1f3f645a1602b6a5" + "4e051a32d335f4bd4c9d650615c655c9ef5a80b76ea138d0d3fc5d6b74317ee8d05c01b5b1280094b1135f1b6a41bd2e9818f5048671c0e0" + "0405ac761ed1fd61eab78d8d165df82fd8382485c2377a5040cb902a04a732e30f87839dd2cfcb908562dbb3801adf95cbebff1b49e8d5ee" + "f98cf70b4fc9eb8f5c2e901fdd8bb49f737cc2a96bcea6c58be3512deefa63673d5b3bbf452888987ea3f16143994100aed15c0e4ad0eae2" + "70f9bdab75802b6874dd44057e80044380044345000578b529400040066bf90a2d00010a2d0003145196440dc83d81f4439577801801fd6e" + "2b00000101080a7e069e563740bf2a0c21088d1e98bb6f405da69b7114b31cd39bd9fa9f8a3d560c971a7aaeffa0d559e519354107b0d6d1" + "987a6c977980e2affe708ef7d87210e1ff4e2a6f80bf320171aa2af3ca7c07b67a20efb1990f161dc2136262b637995d0cb1098699aad650" + "146d15cad64f013d8e12ce6bfd0b26b88f7ac7dde43dd36eb5648e07dc908945cf2d16e68df090c078e2a49ac7296abe92b403834aba392f" + "ca42e24e679d8f9a6531eb33ddb131ddd303ae55ff2be194a4d33517861c44194953c7e947462769f9fbc6267c77a5001c9e319fd757ce85" + "53a55dda54b66d236a1adf94f021fff170e6c79f78f6df0c70d89216c4184e4420423a57c862fc978ca45a5cefc7c483a4a8b20bad295624" + "9ca443fab2db282e2f34e9780758b0fae6ff33276edb1b1319a57e962d380c9140cb1ab66cc6f20080682242955ff1c03cbf83f88ad542e1" + "7df00d728f178cf3a2870ada42d0e2554d9f531ebda44dd28779bbbc240917396369f293ffff3df2684e1c2b775ae5674fb65875a41b7001" + "eb8cdd081ca4a0333fd22bd1f1167ff06761b2b905eb7934df662d35eb12505cdf8c56cacbee18cc0db2cd1da6ba7966fc3fc51d6ba9bdde" + "92c2b1dad4554728b2605bf33928329089dc086727e99250026516a3b54d740e6cbd8c59a26c03bc77cf885271b53f2b05cf80ddfd58f7d3" + "73503ed7096894e860a8acbe45ca804669e677806f1539e83a78b8bf157cc4119a6f488c90014d18ca0e3970e15e47467c4e40b6278948a1" + "596ef01c5d75b828b1837e53a2a61d11545f5072d4f42ebf7223e3992207fd097f49e873a771f338c965e290d42d9e85656cd007b3a56326" + "5cc81d451f07c7bbb105929eb173a68f9db4bd83dddbe23204e30afc87e7380af10a342f13bc093428d909647334642bac17ba3b4ced923e" + "5f4cb24c73b45db5d0fd0c8d4efd41921fdf1d13171dc4e9b86b759774e35529199e0b1f90136d798cd0cc24cfff715c5121e1c4952cd5f2" + "fcb1755f8b3c3253c32f0b2e908fefae3174f2d1595c80226df7ff9cbb2e739fc0db3592ecd816207312327e6457c9bd524ff74b5013122c" + "6c6d76da9dddf18453d1debc6ab19046aef0d78b5fbaab199e2e9743032899d07e58c67cbbc846668ec7d806d672d47cb0c7ec2e2841a41a" + "091be88648752a1b0ef2242e26dadd564558700a378ef7acf8ab19abb5844cf7942777a68bc71a7fc534639a31bd574e94c1b9be0ba48b32" + "a1545d7fe9595b62c5477d6a731fcc42560d19fdeb38e99abde7eea8936b06f724f8a2dc772f4155367011c72d30020fc31fb201fcde0d8c" + "c96c41261f66de063e05d1ff942b198400ee6e3c101bf01d79c25978b62dae925e1764e3ed759c2658f29b8385b62d72380092dd7df1c650" + "82919dda6f6c9b588acbc4c5dc64006ec875c9b9ed153c5e42350c7de78a6d06f073e94da4ae5c91bc9402f5edabb6b59d21ba93d13e80f1" + "eb11add3b98d91b284dc3701911b802d336e90d64a84a310a1dd97701361ef9ad13d0381739b699fbbf3ce0fb8edd526740841525c5e6a27" + "19bbe2069d706fbe668adac924899dc107d64bac40f268fd46fbff978a828f581af05baacedcdc8294b4fed0b9fb73c7baf1571ccd4d7e8b" + "c0544ac917fd7d8d5b64bb91f7d49aa4471f5c192fe488daee786b4320f6a78fc8f3ebc81537e9027bf57882ac64eb044a866fd9f1f97eac" + "fd87400aec986315967458ba64c60f5425f12d9c8087d564330779d88d78072194f55373e460e963e10040305baf47d2580c6ad68345fcf3" + "864cdcf653acf4fa88c14def7378453f9549556e8a5f168a1633418cca8ae744fd5aa21be4defc17a4b150434d1eb3d036573cea6238395b" + "577353d7ae7325a0293c80de065a0d76459b0044057e80044480044445000578b52a400040066bf80a2d00010a2d0003145196440dc842c5" + "f4439577801001fdb96000000101080a7e069e563740bf2a8f2196f53712eff2cde03ea9cfe3528f171e6de1d5b3e7e17812818a53846f3d" + "59e23f3a9ec7145c0ae86cb85d3744530ae190eba81c7454038ebf0da5e0329b5d79256687af97dbf754de8532f6c7ade76a1f6d100384ff" + "2cbd0bdf4f3105e6c232819cad5c50d2a99182e6e28e8064eed123d79e0932825a0e79e4a3d92c417422727ab6429dfaaf4c0d63da20eb2b" + "cd2512658ede0c99f4a54b95bec187a310ed357944fef9df08e331e53c390e795f79248b6f16919488e4d93e4830297d2913b5fb81cb9ca4" + "3474262c68f8d81bf97ddd0e9a7fe5a8cd7cd926bd7cc98581349e7960272947f06418eb85141372f1de7a989049a978441c84385995aabb" + "0deb6bd3d25955c9a977ac63038022d1e91e260659937693c1c144ddae8fa24dcb6fbe5507514ba769d2ca556457b6fd320b58496c5aaf1e" + "861999d557e8645847029b3931f6d4b0bac580b2c1762d9a0afe1cb397a49b6e1340193802f870ad58ff2fa9978d06ca8ab530525f485ef4" + "aac91f7755a79fa0f50700c96d6a3a7eec0166d8caf75b9640c5bfe37086fa68067f428851cc66198fb7299e2041c3c4198a40d84c9a96e5" + "3d552d622e871b6481dca60c9157259c6bb9ec3739282ef10510b7019f8147f24672a6744e397683565aff36f4fa15075e215228da6bc4f6" + "450000c47e7335d82992c4cf4d5aa603b4e2d7e42c3435b271c55f60761bea760f822a0d7b0907a0c0f234c5ea32a9d1cfe5583520d7f791" + "763135fbfe584f40784a8f4e8bb173e5afb481aed5a743ff4a4af73b609ea7e26a0d2a57bf248d0613bebd8489be445aa009a3637531c263" + "942de771ee88501987ee95e6343fd6a03781bdfd113384b529333033771e4e0b18b92c182dd7dc7a2b2469d90639499f487dff03448048f1" + "11e1355e824418e7aca1e976e7faf353077c0c77b1dd472a0762d90c4beff87df9a0e5b7a254989e66003b64459252f42393f52348f71250" + "517db4d13b2f33b5ab8830487100447e611fa0696057a2c928869081fb1c39fa73b874b4cc46ffef530a629d216ece981a16e95fe49857e4" + "b32f4ecb0221cb3ce81b27333471d0d887929eeff3fc88aae10bb936c0f402ea1c1938255f579c82affef0a45c408adf154edbea09fdb2ce" + "b95536fdd75b8d5ca0d81536a3c636eca7b60603d4a93aabc6499bc73a5fb01fcbffa9b6d86b837a9e43831f1ed888cc87cd1d74aa4e526f" + "598aee7d0651c0655774124a1a80563f720558b0e50ed6549a263fe46d82ad51ce045882019d51da50b7a8cdbfc56034643833047f65febf" + "06f8f4ca864e643ff907a2881c85386091b301588c0d8c33f94d46da0b58080da82a89a5611faffd219bd25c9631a2ac94adc234707e47da" + "7b720256752b3451d98291d1b88803ed63065fb393e0cac127ebd7e5c70e607256e55f23b5a65d177013502761fa27d56a11a98468e1a431" + "a39daadef7c92cc432ad163032ddcea91dfbf3ba69688019a9d7593f40427ac554f5f0d7c62557160fd14d8cf463ffaf51c6a75354ed19ed" + "d7380defad7df1735481559553eca5b4d511daa3402ecbc042ef1de0af565a0ccac3ebc7ee460a6d8049ec257b60c7cd93b2009e48d92b3e" + "90ea83baf1be71cf03c2f643c50a00a886ecec90e35159401350e40b217938eaf4ff809ab138d9003c16b0a7d33edb5a51888a040f2734b6" + "c4e2316d1eb1fd101fe634b8e3422c778edbf48555df8e70a4d2f6e6e7723b629d98b7a637fff947b04e95acc6340b595f76e5983b22bdd2" + "1ab080852650fcf363b2e4476429c24b9d7db97052c5cd4d39a64bc717f8d55375566bb27f178ab5ca4a114370c139daa40728e889879aaa" + "114523d64992865db285dad985a90eec7cf77085d57d426c989a7a0444057e80044580044545000578b52b400040066bf70a2d00010a2d00" + "03145196440dc84809f4439577801801fd4fbf00000101080a7e069e563740bf2ae5209c3e4e6a2203f125393c7a0f4936dbebea1b90c30c" + "457ecd4663312c379e7252fdbcec9b020da65b7a9803f65e65dcee2cbcbb3d44e0f17ade7f1fe38d77ea29c35f35d68518307647834192c6" + "0fba7966e179967b9c43de7d4d6b8933d3d4f9f2ec1f6ed723d55b73c0e2fe86e3e162e691347e0c66dcf86c002352662dbece56e1e0942c" + "ffebaea229a07202ab83dd5eb09c1927693fdc0538aa22d943f438351a8991e2098c782396e419b7635cd2cd52047297568e76b03d3de0aa" + "67417feb631f558278aef0be2420b14bf295bace180545f3aa19b1a713f0d43d71a67e8e1c457d40a9ea95022cdaea1f033df9017af46011" + "f75547e8703ab88a73e182064ecf7952d2fe213c32fc91f5b70da3254c613d3e58a29cd27e8602634d1191d4f835731c0e0890239a24b8ef" + "276d6fc8b4d42b579b87fe1b560010d20ef986c104a33d5e0ef1f1e20d347a4af55962820c3dc02bf74342cdb297827fe847b112bf8a5d2a" + "a47fb3a445a0c4c5c6bdbb3a7a94b850f81005e2d2fcca7c1d43052c6ff12e707c24491bae3e048a124785cf3e4eb1d0e7b5e4f1f923134c" + "ceb6f59a701ecf219584778416ba30fa256667bdd6729a999dbd37544e2e3a37c82cc10b3529cc4aeff3e61891b9c0f3b8445db80c22a914" + "8d7d87aec5bbc1e633f77b1706641136f97645abe8afc6e7e9da2abfa21d61dc151a258ecfaba01a4c1710270445222857212cffd8f19153" + "b6a13360507f28cd259b4476295743f15c8a4f713ccd4b67ab6ca557aaa74fe235e803754ab5dbe8483f4262baba08c61ec4c79be0342fef" + "c759b19579eed014dd633c2010fe77f07d276ea88438f5864b2873d86bdebf087493181e6cd61114db9606c9cf5fc2c7a3a76b3065dfb4aa" + "a7ad69c05f8bc75a5dddabf354903c78fd2ddbc16b7b7f00bf3583cf31337f0d835ef36e97515ea34e278eb4a65c3a449745dae72d974404" + "16438eaa2adb941eb6c727eb6ea06f6e21d72878c5d5eb2608524b26aca64e3b64961e446b52a7603759f97d499da7b15d8e596a486311e4" + "1c8c3fff579294bd417a17fa49f62595647b9cfd24a3e83c3e3e4b76a0f3f1f3cc70808bc71f8c383976d81012a56d9e1fee3b72f8f3f9f5" + "02139c47bf39ad586b7fb4215e0ac7190022976e6640d81cd58dc90ac90c854cd100e58605a0bb47613b0484faa16b70744ba0e8837e83dd" + "d5f7f91ce102c98e550a7721565671c9ef754618e098593f31eb7543b38eb93c2f757f5d2b2a81e4c06ff482aff877a8c2d7c18c7bac754a" + "6de8b7c9e116887fb36a48bf401613ada5c57acafe8a8c90cd6706dd11ac95bc3c6050347b610bd1609e4247c43860db8a0906b3741a7bf2" + "ee60bb07c415cb4c1beceb796ed423292359be278f1827170adb019294a2a4b863115d8961833ab21647a4c3360bf2e39d928974a3cd8dfd" + "ccc9a1bf86bdd46546e246485122baad3e25628764f188721826ae6a27671b62ab43faafb15d9d8da86459371c170a2d0e0fac711f813ab1" + "e54c760600055c55f1d7e30f802666db4d7417ebacfa8290070edfbe67db9a85597a8450f9f9fd0214462b14c8b0db88074e00fd9c4e0acd" + "36e8099cbf055c7614ba0b591e8497f1682657d3c674910d59b0735fd4ee728201dfa07ff9ea8854e1547598ce4bfe25ca4df1ae643bf971" + "0d4c8cf15e24ef4e4f0e30486e678060822c86579d51d15c8c5f7d96de05f521e87b39e45a3ccba32d97f3dfa7377f09ea87649c374a4d8a" + "69ca8f8aa4132e81e6c3ad3dff2093809eae7e65fc89e11444c0797ddca6914a2855d51da9a05242c90d8302c56507d0c3e090925ddb3f30" + "6671e62864ac042aeb6252caf6e4889f40e5beb2c9823c068ef8a0aac5944fcea510ecd72c44057e80044680044645000578b52c40004006" + "6bf60a2d00010a2d0003145196440dc84d4df4439577801001fdbc1000000101080a7e069e563740bf2bf7174d901ae7e3ec7acddf31b296" + "464a330426077ec425efccdbada85fe11248cd72830721aa70074f056fbe7b177817d846fe675dcbf77ccfc372a3afae22d8de28074e1c01" + "fbca89dbfd449ad291f300949aa9825f98e28c4e9bb87208aefdbe6743b35dc54658153bacb07e57961344147477472f262e3c6cb95f17d3" + "8bce243695dccab329d2ed8ecfb8cd0647b4f92df286d073e4699e7732f9eda64449d2a3c6168839d4015049a732691943a2279169f11bd1" + "a84012d2ce8ea05f5278f8ad5f355c0bb3ea9ba9eef3bb574c579bb4f3a0eca40bd3a87c182027d96dbb29083868f50038fba74eea5c8e3d" + "4b576a4f30e1ae942f0168423dd44b049d9f20533782fa9873aca6641b9fe2069adf3cad7a5deab9d4c362d5616a57330dd592950545251c" + "fffbccb8c40a5f8305f313d7a9e828922d3f7eea3099fbad4138f47358009a4501c7121bf9f2c1b115f5358fce37f76470c9e19c5decb971" + "ff31235f7750a240c85df33b9117bed8ef9adce5bae828ac29f717c4742fad20e5c9da7d9dfbfb92ee44c1371fe4cbdc2c3c808ef7f6cec2" + "32931fe56d84f82c93d6a48a8194c88557d01d06e4b30ef73e37be083edf073455c57a75887f6498ab3123d8b819869f3c93a33744e2f7b7" + "f2db3d0d92420d69e3fad3199fd2e0509fcc8b1863ab9235726b2ba69ddd6d18cd75ee17ed719c6e904bdccbed17e3c4efea509c5b5ec59f" + "56ef8fc97f9ed2cc8f1f4e254fcbf19d040d06eca584f5930ab5bab37fdbba7726d4a010c621672f0a75736fc65ba8921f73177f79e8eb40" + "fa31c2dc5b1ac40f95eabf5f7abf65c77ee59960c2c6cc975bb420751bc9b3a7ff00358effdf8782aa57afeefb1f9b227e0a4809633dc37e" + "a9ac7effd0868d3f51ba5f56016c3fd31a8284ed220bf6c6d02dd8308a70dbbc65cdc24334043cbe612252ea8427cd0394275d11cd3ed369" + "32a4271173c8fe029a9b80c7751995dd4585456b95425cdf29e8f6055356a1790bfb5b6ac88c7d82b7d78fb266dfaebc8bb507ee0820abd1" + "1900da3b20ceb32d553a21e6bb6ee8a17d2d0dd2ad5f288ac681ab45620352346a06f32cb17bc3c8fab4928ae438f2455dd9b9066d9b290a" + "da6f7ae5fea9ce10b619ff9e1e6dc1cb021002fb0192cf6d731855c14e88402dde4c20584c7efaa53c06bbc97f9dda127b36d8a9fa8919e5" + "583ba82062abce95834727badf7ca27202e8ff92630153f7b74586ac1bc09e805b73d2d3387dd69952ce5bdf1278761aa1ac5cf68a679f6e" + "a453e7bea383c10f8522d42724794c6840197d92084fcf489219df3d8a94db5901278f74bb0f02839a682ea3f15e831783a42a4fb090e1b2" + "63d8cf80ae3c8fcd26d4679ee66e5e7165001900c3994b8278f733dba27672b5105c84cdc38ca8724f327dd8edc87d3aecd9532f2137fb8a" + "1a34b44a459f3a93c32ab76b1c2dc5641c66a68725e2fac5952254adb7188295e1ee58e8cc429fb9b2f128ef87a241c36790d92c21f20500" + "c06a95ac9071b4b817a75d5a23226fd267116d43cafa476f867e92ad6ab623d7e39c3cad1b0dbb37e3817995340c499111ce079762326720" + "89647d026c9e037a483e0da6d32829328eb771d9c34f63d55b8c79f0e21d0111fa1e398d9dec507993ad3de2cb4707af223793d33f9ba326" + "f53fb3031e48acad90d8a38293910c63c2ccb5bf76c691d45bed94bb039d3b01187a7f34dc7c32dc264d902d2cf65cd14c477acdfd295dbd" + "796ceaccfb2999e8526bf91913c0663c4c13cb459c650b7977af50b605049f67315e40c96a592d872d769926acb4bae01b837ccc08577898" + "a095c57515680625457cbdb268a866f3d5671575ff90e56b7c7da15604f6cf6126c50d921323482aa899f8eef53144057e80044780044745" + "000578b52d400040066bf50a2d00010a2d0003145196440dc85291f4439577801801fdf27200000101080a7e069e563740bf2b91bd99b9db" + "f617b69b6fc454a86bc9b0f467796769a4af16f1d44dcea11f99ccc51d14c814398f39992fff7f83f06adf882fb530c8d2fdfa3f97f015c6" + "0cb6078cfeff4589c9a5b6dae80c5331147aa99e0628d3c3bdfe2a9b5beb8037cf8b785f2ef27ea8cef333af8b32d014c0721cf9b0509113" + "94820e9622627399115f429b9ba489124d49c6ea9a6d5a815a80ec9a3c147bf6becf471f5da05c4989efbecf39d0176f0a9dcde5e1bd4751" + "90bf28d8d52ee6be08e9ce1e95414cb06d4a56626559f5dbdad6804c6ce00310f13b44e3452824a39c967ee1ff2527c3028505e16fdf4887" + "6a65e8d21cd007e70f2bbe7922b708b6bde5ce137a099cea6d40c45659ff461e5c2b95691884d595a1cdde8ba75a5ede653f5a865317ef34" + "c828b67ad19ea6bd102065c99768308246b6ec31fbac60f5f5329b91325e69193b00204433b84f24fc8dfa44bdd35375a2bf895a119dac03" + "7df8487b6068d60dfea0e374dbc137e8a6b1c8e8390b8eaf8c30d2724d6da6ce23aaa888b5c930e525ef80c3c0a1865aaa010b0e4af09f44" + "5f38fcfe78bdc0896720a23cde120c6d7479be7c0134766ebd46054ecd6be44f160811b5b3cf243dc5a9639f6f2686f311c602bdb1061229" + "afbd26d185149b9c23ef13d8c15f93fe80ad84c76e6c0dc5442069bb3cbda516d93df26392d1fe11c5c0d0d85c614eedba5c87b79bc87ef1" + "c1f4a4c4ec5f04fc5c320b1790763323843f682731b8c9456e998075bf0fda6bab3f076918f7d4a2a8b52d08354222044781071e85e9d9fd" + "c06c949eafc66dd730c6acddee834b8227115043a59b022fc1baf275895e4ccb3d73933f32fbadd7f87fda314f5ab7a9c9ebef7a8837e53a" + "29833d1f581f1fad5a7b4df669cbeb9f975bda108b47f303982e4f47abe32abbe7cde1f27e19ed2283fce8d676f3d13c77cefa617654c7b7" + "03f010f3a6f9af1e4362c097d642ba1c00ccf4057db7994e656fff20d9f689bb99415dbec1853c0c134822dfd691c78ade69cd9dc4c12734" + "4b717a1eaceb33e3667598464bc4031ddcd2340ea4be0d0ec1c4a25f4e2c336660cc97b2b7b7379991487f9cc032a86bca3cc5f4b5e409c8" + "3de197680e81dfac511dff3a838c489ef27eaa17b3212e77893c71e817b630f0aa46cded93d9bc6983ecd521d066bf8c1d95d9154c6c6aef" + "f82364be2d0cd1323e41da7c4183e296e7d6d1d0cae47ccf590b181e6d1110e80ddff48fdfde75408808e9b1aab0613f904d7b2900d24baf" + "e6f868552f209890cb41e05405e4963e7e1e2421186de68c3282d70f60d8624dd235656dea407ffebe78d7c1920291a6f3d0028583dc019c" + "05cced92ab04c3eec22491545cae90911cbb27cac3b976a90c7aebf8279e858d025d87d381af1ea1cfd6cbf440b9d7a415e50135f4b2637a" + "a255cfd5f774dd47594225ecabaa367cc243c12a8dbecb075e13baaa2434dc7a4eb300698c88b1b4df751bf9cc2d0ca7966c6d6b916d4b2d" + "25637f24e33ea2cded4b26b1b7217ea4e8af489527a44073f39a7cb3e4469ca78d376f41aea63404df307900e4a7db139cdb2e121c303adc" + "870205c70ae12b516536ac3be02ec395af7e81d224562b349cb00447ea98b946704b0ad83d6f5da42c5934555e129567bc3a850aaba35a27" + "226c36068060b186cc955247e87e730c4c990e95a91b3a337919276ba5b1c56489ce781808c32b7e7cf9372df51297eb48ee6ec50a38757d" + "d72905cd5c70489e3b810ba0f7aee833d5ac31fc297d89e4ec49343eb473ba830cab6538cd805c46483be6d51f49ae3e9ae15bf74b915960" + "5a21d411d55e0ef8da3badf2f75654aeb959d0064e6fa3b11a49aaee055a82c205caa8385e170de87fcb139ed745660ac14a8a5e12d13d44" + "057e80044880044845000578b52e400040066bf40a2d00010a2d0003145196440dc857d5f4439577801001fd3bff00000101080a7e069e56" + "3740bf2bc15381330515055671653bfd56093f4e59f9b3b7708ea18c08dbd588ed5f18fff368955eef5d87fa5741c6b6f1dae0325ed97a29" + "183f22484dd9e6d30c1110cbe354146e35b0577b23a505bf533355be5c0c07453b63b73578fe3d40e585e7632b8d24adc56756cd2a7f6cf7" + "e4c77d4f82cc3278d12db44f7414f9dfdff05391bed41c0dbca4ed86642d98d34809e5eb3963660ee1c4871f8206bb6411b00f4898e29731" + "45acb1d689d352aa23b1c21cce7081389942c895ff6f0a086b7a829d8fa57b721aa7b6efe4d0c348d70be2e6e1d8416ce322bd5750d6b681" + "712829327ebcf0a9acef63844de8d4a7e92c0d908ad2b100c115ebfd91d468eb056876123dd04378678a401f72965629273194fba3db8f7b" + "505165da35b997a4eafbffa0f816e15d1492aeb1ae794dfed767197447a75a524d69d96046d9c45bf90eef60adb73a0cd0c411900638f217" + "00e65e30f3d0e922bbf13624d5d859a9a5f4252595fbbea7ae57df29628107c7ae58055e6f277922f7cbf957ed3123f0eeabbf0243148a13" + "36cb5a8786e1fcda9fba3128e7e35eedb785e69556921c44960f3c251977003579c8aa4b205e219972024b4f47f6b758dee2611fe350f838" + "e55842bfc2e10eb3ad7897422a783480c51802d88d50611797690a79d9c35c6ec109416ed9c5d9a92b539be92deb202a3fdfd770caf396e4" + "811b1121f9490c01eb60112a43dd132c80250b79f20f5a8daae65e1d40fcf90f2f5163423e8a71b3df6c9b21d68c83cc967cec96baaa36ae" + "8000347553bae8b1b098f729bf21377f571c57d5800656f17495c05793a8567298f18bca90310d5a242a354837c84f8dabd227fc9566b787" + "0ef08babc56bed0c67f55455286bef55cf35a6e3998fe6fcc97440751c82ae26c0bc706fab793296147067a01b009ea23d2646abb2ceb3d5" + "7245cb46d304162b13e5e95c0586d1a90242e3626a5979b61433149d8bc42869a27f16ca7dcc92fcd0240eb76b997f45631ddc2319b05e59" + "f5bd4b15783967c4cdcf9073eb3c3c9b17fa7d27e74c61e8fd98dbc9f92d8621f2a0e54b977ebeb3d0a2de3fb77241d20fb4f0c2d1372542" + "bfe003486cdcdd45e0327024bbee064b1fcbbfcbe1f6f3c483e77d4144128edcc8b28385e70242e7d8c386866abc5a7f0f161a0feb60672c" + "c93f029ff619a33c84fadc3c1832a15e9f3c470f9d32c531c5d34d19839a01e413758deeabc243e31892fd3f62b0a3ca11acd9308eed3832" + "3e464eecd2b1a6326b7d799a9c6a3aadfa2d74fe3a359fae773574c0937d19242cc44ff0fde358a69690e9dec48261ae3b14b0127737f3bf" + "f707b90881e10864b81d98732833f7b4bdb9f42a1d933d3a10247c7e22021298a48731ac02f065545e6eee14e70ee3b809d84be48a986f75" + "7f229d82e3a841fcc1d306db1f2239d7c59e99c6dbb429f93ad6985df130599f3358890e3fa480566d89cea6a8a27fd17fbd5b03cc97026f" + "59ce132a5fac9aeb67c352e0e8fbe85da4994af2d8c79ba49ccb6704231459d2ee3833db5d072466c37d0ee96bcc70d843ace90967b204d3" + "c534ab04e2f54a3d48f0f1a3161e8ae31875811d51ce9eb8208762e9438b697acf52ed5e4ed463e310d2108af5f0833f57bfa85e9e46ebc7" + "0cf164040d5f619b9f4fb4d8079852a79a42144c1d96c25546fe6ccdb115cf5005dedfcf72f85db0454b3786d95dc4b6f02a58b5db6c3578" + "3736562baae5412b0bf45af3dabeab68f208922312afdce0d92e4a59e83b5d9ac077dc065c2312a4ed9a5f499f50a9647186eab8da1efab6" + "74e0a848d7519c6d3d65f572a8e4840ca4cc0f5b471a5d1fa9f20674fbe95b93d8aa54109fd8a3a5b57b9feaa037f18e05a241420ce8deeb" + "1ca08df2e082cbab44057e80044980044945000578b52f400040066bf30a2d00010a2d0003145196440dc85d19f4439577801801fdde6700" + "000101080a7e069e563740bf2b280739aa638e0744a7402ad2fa31ac1bfc444c29030f691403c01f2e0a1100abde7a1f9653c815f5d61465" + "9dc171eaf3f187b04b324bc71b50d1d3f5a6d82ba1b237ce56ffec743aa30d5edf52d38fbbc50d9d2a930d69fe8e6ef473659b548d67080b" + "cc83e5a3759212f2bb414180216744ce0b71d59f29184c9f2ac63accce02e6205e1fe0be020ab25cabed6cb72104f6f6ab0f19fe7ca67402" + "1c44a7db3f4a95436d234510d789c89bb4cb2e25fef301b079bed1b1fab31df7a45d85bcbb17ddff956d06d568600ae6332111e0db308403" + "ec426e8d75c24c4bfb970be95e41c01d4574dee0db362a52b8a65cfc9c42a9acb77f280d416db4a814f643d156f7e92a9adda009940cc597" + "3d83c9fec86a8d8323978cc07d60e5ec268dd3505306491ee9320e16c20564251d5045252a212e1bcdcf05523d9138345aa17828e4095686" + "8dd54d2643b5d8ecacb32fbf4b6ac91b2aebc329bfb40ceb68ebcb9e5b5063a6a8700bcd534eba7c3a9e58fe04d45e9cea6b61338f804f97" + "4bfd4a3a23c8c74da9b5994b09a29fa0d59c563db393cf5b586649b967392bdd8031fe2b80fd82ad315981c8b7663337b50cc59d02ec3ca5" + "34738d82b34d06f4401a41816543d4312e4f87f0cb6c9f2f74e5ae30d56176174f0cd8b8072405d7e223a1d2c304150b230041dc101a9c8b" + "8e5c2ee848bccafad8c42e6c58182a0a6386ac642cf9a772f996f0388a8d5c4391b5845b2a2e5573bb290b382990ad5e42769c240c4731a9" + "a0371bf0d3cae364b11c504f59c56247ca064972c90e103862aee860d79dc7ad4201a04657760ad4ba0adf5179814188d734c724013ce3a6" + "c9129b11a7d2b967a8c0f96414f694516bb67ce39045b45152cb1e21ea5fb99cd11609eefd742aeb38f6131477784119f7f837b490f4057c" + "048e2242c5b07f88766a3d636212a78d80b5cb134c91d5a5dd3f2235cfeeb4f4f55b75999b70bbe04b4cf1efd94c3bc185fe09e38f6580aa" + "8e644ec2a34c26c7aaef2d8659b17d08aa294100d95fbdb9c59e372b012fff1d36d55fe8b0afa21ea54a59c83d8822f9faa1a5532e3beccd" + "7cab2dee44916ec4aa0133001da6f7fb39e9ce920615ec5255b9d776094026ae76f1191734ae849a9e36898a3b99dd775d793d4037c89f49" + "87e4cf39b5b0d81a5a4a5bd2f14d5becd602696ea1b9ffff0814677a25d78e664c5c05c3079c9932bf828f318aca1e00c415d18020c5f8a9" + "a0efdc51b98548d054df662f7caac044ba37f3c6fe55bce99147e4aadc6f3c3826b7b0dff349da90f6a11de6ddb540122477a673d2cd9c60" + "24cce8d8490008b9f3f657ceac83c179f878d25d34dbf26fc427c523249da542327c7f46a97213a5ea65172d43f34dfc517416ef9756fbd2" + "1660a28f256036d86834d5a24904c584991e42db81266c4012622a36ad6acb60796675d85a9369ce648d397084fa1fa54271b5a6e068c208" + "3073cb5054fea4bbb231b48928bdbba51bcefc55ef720bd5b35735cfa48eacefc5fbd96d3fe66d90754b26b006faea606912cd88d8f53153" + "02a3bd82cf0dd4b5e35e9764681360b55f4d066f0953be3c8bb03792e01bec7d06ab20a074d296bb1ab025d217cffa2227666d1c51ac021b" + "5a72683f7f784d026216754fad6e8c3597bf922d9673bb41163e828a20f1815096039b39664e45a911eedc8361832f622c4302582518b467" + "4aa67e15162f6a0d298b09c6c282ceadf0a1bb969cb17d25943702cc142d4c80be72c5b9c0aa770119f4a8f4f87e319f3d93b22440392fe4" + "979787c02d65e690776a0dbda4c659edddbd4dff18503ffaa23552831c576493bc799d6165e7d9d1665a6e70e0f4bfce7412185b3fa3a89f" + "30952f32c9ca4322d4e2a4871f01412ab844057e80044a80044a45000578b530400040066bf20a2d00010a2d0003145196440dc8625df443" + "9577801001fd252900000101080a7e069e563740bf2b3e3cccb1808c5cef03da8a8f678979754acd488c6512f0dcbf20585dac32534d23b1" + "3dbf27538768e9305ef8d32d883c4ce010237d8c401e8ea5923f62668b697c575956ea9448cc9b83a4dbd4b72a2f6b6593a3a7d700602c5e" + "76dc6b7c4fa992c714ba87a8fe9a5bf53e72369055d34093bae7e6136d13f293c438d5f62d1c19686a4cc4919a851db604a2c9f5407d2937" + "e4cc97d52b5f69cb6edd63358d02b1b15b12849978a9c50da54618fb617c9d706f85957261d69b9ae841c3797b73808abc48b4f46a34c038" + "5436d13253552668bab657eff81ee8482094d87b4cfcfd09c4894d5d77ae6ce6d05f26b48fb271e1735a2e1061b74e63f1868758fd62a855" + "111cfd10766d7eacd128a18c086178e9451822b88577ab1e68dc32ecb60e436c8dda5d97e582c3a5f54d4a77e19a5307f3a6fba11c8034c6" + "ce74c26dd299239db9e85882eb9c62d0a987d79bf66bebf994d91a6d74e145bc15e2e2748a13fa2ef733405f19ec5683f061548305185765" + "f4aefa6db6fce0a3059f2babdafe50c483a6afd3bd1beac674fc91b6db29a9ce06c19032bdfd571d699d1813c82e5ffd84273e7e7978cebf" + "48639aa4ed12088cbfbe70543efb1e7210749ab8cd512dafda711c5b18da005ff34958afd8e1dc6b73ac8fb3ece79019a5d714d1dccc8ace" + "714816ef321f4c91a059abdc33e051ed46354825a09815076673cdd41c27f49c43a2fad399a8ea83a71bba73491207ade9da3cf228e9f397" + "1d7e463856a01313a1543992f756a669654bfbd8b31195572a9750164076e73c13993cac789c2f827432d0ea6791ac4d68ff399c86734e76" + "cc316dcb259b7982152ab5282ff0870fc77f61d4d0a9f82202dd17e35616c3bada28d8b97bdb56645bbd0e05d0bc71b860a4041c6f72db71" + "c26e8bb8b601fff8d971d0428d14c5fdceca6bacc2ffadc1fc9bd3aa6c635399c515d948fcfdce219ae1c49fc797b45f69c002d8685e4e7f" + "09f5b823919e0ee826b83af14cd62a2aeb2631a6162c2bc70da672adbecf8ef30bfcfdaf81741aa7c68bc3137b37b249288fe2b3dc27cacf" + "576de4fb530e05cf7be976eadb5c5a25f10aa121aec317a0c2e873c62cdce76d143c0f4c575c5dae91d319732688fe9267aef83bdfacf055" + "d353c0fec24d5f7670eb5832c8833988384cbca4584b7db7d6b9156e89b1b42d2f90fa2b25fbedafc895b08b5d318277a9e0c80242075513" + "662b765d41312c898a3d0325e922a79b4fa1b403f5e10b0446d8ea72c6411f966da69cf495c8b79d6da8241417c735fed0f50ca41fb6ae1e" + "a4d19ea477955ca3f259df20c525997d238ec8d6b9a1edde0de7e050664ca6f60bf4894d85a80e06c416dd1b16f8bb068581d97494e1d25f" + "ef473b061c1fe3596cb4da02640e1f873abb3d132259e59d32e014b77a6b2a2633b964d4edfd89e5669ed1338abf1e65a3a93abbec0ee441" + "142e99310534af3d7430a01417d078e9e619df5cba25f94c6f2b1c885a34c738dcdbb2d9af5967e4bf1f88f85cf6985c580d6f714eeff2eb" + "99c27d6878bbdf086593ddb1597da886aa34c2b934aeeab8745f1e89182e4889807e5ae0fc4fcc84901d4404ba0ca7667e0553f7c0e8e792" + "b6ba794e55778be94d69503144530c0023a7bf5e2a38ed0101b73515c5b5840c0e57c12b9a08add4d4707635c80c4796e52af51f30411775" + "ce1a1a2ce543f5e1111f9ed48fa6a43796573bfc1cc5a392cbb51fc5911a39f282c91e677bf7ab82d587e92b7c6ef048ee2f9ca1a0f95133" + "e540baa131bf5834fb5731353baa9e7317792cb2726fd0e58d96500618d909a4fe5f12e8a240b25710fe23332b6780000126c0f3c5bf065c" + "ca030ba7c29c5695a34136a31f430cba3fd5ad7ad2c63d9c245d44057e80044b80044b45000578b531400040066bf10a2d00010a2d000314" + "5196440dc867a1f4439577801801fdac3a00000101080a7e069e563740bf2b76ac9734d1c3b4f1f88e7af0afb31110f2fb31da39ab1f5f8b" + "470e6b48b736fc6ae6ac85a72bd023d4044dfb76e696809400e76971903d13ed52f0107ee8a7746edf0d86c7436ff00d1287759d5be64f3d" + "86dfdef452d810e17a60749f02db6676b8b7d1ba06f884c6094654dbe9422a4887e6c9215622637d05c2037dac929f47d05d479756aa8384" + "2c208625671070ab70b518710038e0ec1a136bbbdd536972b462562bae2bd6bddb43a135da51f76ecb699a3b26ce890bea0ea16a16119973" + "72dd399a2a00ae3130c29d9ed88baf242ba53554b8e033a72b570c82cb1c54f02afd35924e9d0e1d898cb3fe2964064b479d5b592ed0f60e" + "c93c70a0ee4b4a5e49a32c5a12eb78f13dc3c7be319d5da1149fd278107e4f963cf978704392a7ff70209db75a0a2e770ffc27500ba79a99" + "dab53b81234bb28fad6901bd24f245c7b1eddb4df03dc6fee69dcb766a2a9232eb8c27d1b00a6ffc8d1d022192a887cfc91e084d3431874c" + "4b6a09b0cc76dd609309f5082ba2c526be65c42842dfbfcc90ea89000c440b2fa1d277d606fbbd712b5b716a3d78b278eed6f0e3cb274c61" + "3704085085f3aca5af2e82eafcdded1000e75193554421286fc3ad9bdefeb005cfed5ef7811651003fed0c4c06b594b9134aa0aa287e59fd" + "2d5a81cd6ff74e453782bae25cc4c8606954510081f2fdeadb67194c55f40041915249c65b5fa92bb96789ade0821a60102046b949b96eb7" + "adf876b0df7f4a51146ea79e51c621acda6586116095a8c8eea98c6bab5fc8985a5c84162139ddbc378753d9617f663235a0fd762cf03ecf" + "1ac3ef3201520cd12ac0f5233693eff7d8c448760ce85420d38f18bad7b37c5d899cf47674f6981f01b12d534c9a7c1c6ef63584a6736045" + "538b4d69713c6851e00b3614269e672815dce462d67f1bce0a3abaa926722c01c011ce5e0a8737877dcbaaaabca2ee1a37680bf260b308bb" + "bb90c729e646a271462a9c1b02e002238d15f8762ff8683ec7315aa6d6651ee8d00ad8561fd8e32dd97536b33b275c44662d050be81d814d" + "08050075484be95456ff19df9b0b38bfe45f72e23275a954cb066df3a063e7929e22bb824bfeaaeaf69f94ee82284703fe451e1bcfdf789b" + "3b06225db816df06869ca0df026ede41739aacbaef8c3d039f293ecad2cb78b8775ebcec62c24b42eca4f7df537601a275d0f575be06748e" + "52e2cbdea20a76d538a4c7a92fc3958c0beabfd972da3ad949ae6a038e8589f6cbb3ed4112adb9269d9ce6fe230d99b6ccc00d42877f51b0" + "660d84df11804ddcd8a11068bc2d6200d32c72637dd0c332e4ad8fe3ca33f3a5272310acf345e5828244c577df6441a89407fcd62c69eb3c" + "5dddd3108f29979ca9f3d72a4c30a73dcedd241cdfa1b2b0a7900ff70e2c870809ad6ae7085a455a1fa4d22a7c625500880c14eaea791775" + "7747e23775b4a594c536e25e47911fe215592918005ef74aecf98938ab67e3b3838f4324ef38ce43748062f93a74815bf4e2f2f50479e0b6" + "16afb91d0bcb57b3330c6e8df718178a4272e651eabd8cc620187889cac80466c9d6b2af7bfa9d1b045a9807bcd8413e282349637c214b84" + "cec1a6aa31ee0a5a287cdd1ce0ae5ca3242efa3bc85a479c53ba132078e5157d008af6d6bc5c3266ddd6a7f2fdee30f63e8f939862e6516f" + "373a06ab8bcc5e3ff7fcb04fd00c1476775c52196f4f9733a2eb8f201d9f1e1c5b85a735a0596352d2643c70b1019a5603a33ba7feab8532" + "2971c96e54fdba1ecc139eac082ca8e390763b26ebe421a230efcfeed2bebf2cf5535793dd947c2e4a674f835b76c6e546581b664f3d614b" + "9ce14ad2fa303d92b1a2f555e4564129aa29021aae4e7b3fccef2aea64ed7391c5a4bf44057e80044c80044c45000578b532400040066bf0" + "0a2d00010a2d0003145196440dc86ce5f4439577801001fdab7100000101080a7e069e563740bf2baef91b710cac6f6b8e915a1c2acf5a0d" + "7e1d74929f0b08a61b47e3b5f81d4486bc29d333e747bf10a85922422cbb3738eec3bcbc92cb13a3fbdb54ad81487d6e27600600317d09f2" + "3ba1e97acde6956d2bf67a335d05edb243ae541ee9195df66370b01783dbb8d936be7a53761c11679523d9f52704add361de53051b60ae5f" + "2dc21740c5545e35825e9efe9a1a9b2441188fef2870c781f4f11b7d5435ca4012d57e781d2a84eb796fcf73c5299342e35439ae7d981442" + "a9fdbdccc4f10822f48f02ca88abdc326b774277fbf932dd79319e90a66a058999057ed90b5318898ecb9260bf3ad221ff3b5f441bdab3b2" + "cc67cbcb8b4fd3888e3918781f8cb3c41d54fb6f7b1140a3d73563b77b51c51c7d46d4a80aca45aa6784a357c1edc60e156c4b7a5f11d6ae" + "e49d66fe6c90692226531ed0a8a2fc572744a2a8888e9ce02b540397b8bbc6cc7f0e71171e11e94fe1b84dbe8494bbd47dc19efae019aaf1" + "35eb74ef56c5e641535e2f93537dfcfa8f694718284c353ef186ef26f1941f7b7720aeaca2f354ff899d6edd248ef8abd046e2d42ee2575c" + "bf62c32dd66cce29b168e6ab0347ac3cbc20c159697edaaba86c8afd9b284b37a3f6baa406414fa8bdd3fd8a0a13c0e58ffe8331e06f2ac7" + "eafadd516ee84919d4f5fcfe9d26883cc9081d31332ee31ac84f3111f9a95eeb2481645389e1c57413add83ab7c9931f46590820a9390275" + "bff79b3145c8e2658f183b578faf99ae588828dd7a930dca7ab4c2761dd62f63c06961b40019864dc2bbf16bec673a0b8497caa195ff3c75" + "079e510e0ba56236ab3b54530430db0471ba4381e16d8fea3e288559be69b44d070e8318c78f766920ddf047fa5d4ad04d17534ddea0129f" + "43466ff5591c6fb205d7d66213226b69aab3e6acd3d5e9dbcf479288c93f4a804af76cccf852883e42eafcf6ab268876ac1b00238486992d" + "fc042798fb27acacb0fcf7d016c36a9f0a1021bda71df49f47245a67f2ceb7804dda0838bf48f7f60a2812d8c871b0181416eb7bf61ece80" + "7365341e36800f8d5a05c24442dd87a8941c5da73260c3fb5d7010be209701230665172fc6a9bf0da67fd8025835079bc6376f01b6df8010" + "2d1cffeb188a1271ddbc52dcc859e3df77face07ff95e61e437328dc6055c055fdd9143b0194a03df8bb67dcf7852b1ea2403fbfb2fe8a99" + "3a572f4eb385c33f90f3d9d1d78d7db906f25dfca57d1b00c4fa49848516ef75d8052f2d9b402250abef1002da37b45d32e5b3f14906a9a0" + "e0c112eb009dd7201ec1a715be573f8dd542badf7dbba74255d984de2b422a2d7153f7eb8bb28a79523a3b5e022f0d21f283e13535993a23" + "fd5ed87d109f4eadaa2b2705d3ba14aee14cee99f0dca853fb28952e4befdc6c2a8ab45eb046ae036e06e93703c6a2efc4712958d9838c2a" + "959b88804d098d4b0b156c368e5618ab120f4a96928904fa292c03f7fcd7ecd762864a41305dbaa0b0468686984d49d6528323f9dafd0d41" + "06c80af2b090f1a77c20b8fa9b3f3f217214073a4948059448e6356e4386b37705346f7977873d28859166204adbe56ea9e2580e55ae61e5" + "cb0881c1612b5f275e96755d6382f3a36be041b7f56827938ec5e06c92947a912904074c41e8eb820d66546dca3665e431579f335d850c2f" + "cb73c6517357cbf1cb30a26ca044f3e6646bbae5aebc0809996611f846d9ce248759190eebe68fed2e113ac70da7188f4b8135057039954c" + "2cfab38d52bd90401fcc93201427c00d1674aff3e61efad08c5a4a5d0f108a7fd76d61e5731c3ee68330f6e4f6b6ad9886b801f7521fbbcd" + "f2f2edf73882163011c52db59203b6c10dbff5f0f4292a57dbee22df7477b73931db8fce04da8fe50ee9e36244057e80044d80044d450005" + "78b533400040066bef0a2d00010a2d0003145196440dc87229f4439577801801fd22f400000101080a7e069e563740bf2b8b57faeddbdaf7" + "27c92ab4ef51ad465d9581f19789fb649a2bfbc99dcbd4a07e2f0a1e91a45258ce80ba0a3787af5ce2dced44939e9ce6f56860b3e69da4ed" + "818e283469ee03878b248acdf4380aeba8d1aa09c4288af8c5b252d7299d17f415cfe8ddb0ccb7b9565633c32573a823eb6bb623c6f678d2" + "5a5d8555ea4eb916ca67f3ea236a9bbf69be53fa88aee6ade14d809fb489a908cdf7f48cbb190f6ba83937d3cc747d97256da91c849e739f" + "64e2a01f692632fc2860317fa76a08dae7eb30f63da8f25e759f364cb4e49863cb7bbe13323581ac02ae09ced96ec1ed384b1786e1f9218a" + "01fb44a1900c627d9e56ac88f2b9e53e3d741b8057a0535ab8b59b80d8c0a9034d641712ce49fb2de7fbaca36ceb12d00c243b26607a92f4" + "0c26034bbeffa3c1edfda9f4840a6ba83615577f084763adcae3a420d8570b521bfb04323c9a9d06ecc98b117e9e5903b927ad9fd5608e26" + "180ac7120bb63131ada7b260be8f2b099eb6d158debfb3117ea452c17c7089324b1f6828c5f0c7652fe3d557119e98fb22afa36e20fbb570" + "60fefa623db0fd26b5a6c3d34e2a449f5ee74f6a9f49d9174ea22c385fb1f25e2eafc3a9460210f1fd0447289ab89712545b4e6f322831b6" + "0e9e14e359c3bb7c81c446faf8f15a7ff9a984aad44724357b545ef8514e4e437e0d4bdfc43688701166dfa9cb0928407621fa8ddfa243a1" + "4f0486c00e449a7ce8e8cb98a41d748727a0bb0360156863b39b8d01c24e75afa6aac016b281e50a62d294592d15b94f070009488751e9bc" + "11f09a70b1ce11ca1206a6600ce252f0dc19bf0a2fe0a20ef7dda172804b3b565c88bf889bb30b591e0006e2b9df2dba537ed9411e51ee11" + "d6e3bedcf2afc4799e72689996ab0b1ecb6b080a97014ccac6df014691380eda49e30567eb7192fb99f6b0b89a2da57dbefdefcd4e13ca68" + "4922faaaf21a2e7b719377699a89d5a7c65da9f21a49ede30dfab7305bd872eb74190c69766e8ec301e42bd909688fb52da5b75340b07277" + "72e2be0c075a3dd8ad520b6dcfc20d1510a483bd11f912893626e599232ae9f5143faed2b78addf09e490e06d2d1e5be82c11b7104b89030" + "40e2ecd96e9cd67eb882fb19b22d1cab3c585e27e9b150af90ed5a90fca2531101ea8222f1cf8dc2fafc54f966b27a83940a3366f5d50543" + "b6ba78f9fbe5c08893a9f7dfcee7610199c195a5386d0faa453a4f56a82bb76678e4f4d1be421dbd2c72129175022b9b72de942b23d74866" + "5d32a5593df35d137f1558cae59e185120f9860c30616ad1696103676773adcee9c6a618b30fe3d674fe436a5d9eeb41dd6c725c589a6161" + "45c87412f0cc6b07b1f68ccd591bc4bcdd8cc2cd26d1b6e1db9358c1c765a75955470b0347d8553c111a21d1e44032feef3000dd936eee91" + "27a0997c9547df2e9f1fcac6a71e39b632945ab425471f3712eb010858842e89ec268efcfe8a8094ca5b6c138136f568fa35203000142c0e" + "a22c210e42d07677b955781330a3ca67380071ab25059b112dfbbdf75b61050782a98f7b0c3db8b485d5d63bfdcc90694b12e6771b059bf7" + "53c0fa7f6d7c4b4582b73e6b26a43ad399f4f07139a203156ab90825c8129788801d2539b237401345a06c9d8ff8c8c2135e20b6f5b140d7" + "5a9fa46628daabaa330d23072f2625e0f6d939c1672fbebd818f0f151be2b22eef7f32bcf122e722184a23d58b790adb810bede3a87981ff" + "951ca2c5a8cc95cde85e088b58958d3d4e24806cd46f88661fb3240f18af4005d5eb20bab5f83a8a6e98e59e4ea838f96c8e52fc9ab162a0" + "52c77e55afe7cba1d3d8cdd87b5d16087baac94601a97bb13a29ae66a73f808969a1d8051d7c26b6a38f8b8d5d57c075f1b6566fd744057e" + "80044e80044e45000578b534400040066bee0a2d00010a2d0003145196440dc8776df4439577801001fd9fc700000101080a7e069e563740" + "bf2b4a2f1cf1787a5a3675635ac42fdef50f2788f73fda7ce6784c4e94114d497842307f92a80bac28466d0b0ff2ea0fb3fa2b3944926fa0" + "c4f2157bdf1df94a610809778be3450d5bdd64e837d9af4216d54a75e1eb2439dbe1e9b2d4c327a6df3657fe88368b8d199c691cae944c9a" + "872a2d00c93b96ebaa26106c6149c1e461483340bd6851348f11db933cd483d810d3625270e28df344b1179d3cbb652715dea6c135267423" + "d543b37656672e60cccbadba3835b339902a9148b8f671117bfcb48f8059a5ed4ce28b29ee28bb0297684c197729808b032efe160d2b9228" + "4f67945840aca0ccbce19063096cf8c78b6cbf8658f9f94652014eb625ab9f0619905ff2a510aa99be9bbb909ae7159b04e933daafe499ca" + "c144ef221343dc081ef1e140ecc77bae5a3179f5b519ad95ccf94a0edd0fb9c4d5bb66f6b59c6d7067b219e496d5b2d1196ce5b59d003cc9" + "ada070f4675b66995f9b2b346217517d0b98afac0fc3484c167a15a5b1d21db0c644b25ca3f254a62f55735f51cab10b7df698617703bf99" + "95083886d233dd953d0c3e4fe79a0cd0c9f14cefaa9c6ee27ac86a8f67fab1b74096d269d264e3e281aede0d63eee96ce5d11e5761df3ea2" + "01eb1e27c703c7988d22739c81dd2336202b8002181f04a38c223fc1a1426b47cbe399fb5af605672a94f486bff0f96fef3a18583c13732b" + "9538e61403d17583cf3766b5acbc57d5b652d27c957794c6c9f9ab318ed7eab146b32522f6324305d03652f3cccae64587be8de2538daefc" + "c9ccd9cabfbe527478b8a84bae4316c7068b5b481ce17a42bd63ee7f15cff1da63fba07b4756b82055efc50a2e7040e3b45e05b05ad8d7cf" + "4b23aa9650140ff996f1506e4697ca7fb0583fef1c1d9a9528b6bd508b6e67e9d1fe9c90a760f350b88ad6b813f30182666e16efa5863923" + "a91446b971aa5d6cad839d4519658d246791e281adc8e563116937319e12902db5d576e68146ecc70860eed942035820fbcc79a0fff53dcb" + "9a95fffd2e46a2307d76388b3eaf15e561895d02eabc82695c23a661494a3cbc52eb5b029b42014e62b9d92550b330d1b3a20ad80d5f978e" + "d99b2cdf8570eb461f6858669cc153c8fe586cb6b521b6422c481057d2920e50ec73533bb78e093e726a92d43f790585e74a9e3b6789a0b4" + "b4fd36826e8beabe10428da1c051939851c616b69b88fdb10b0a9c161b6bb23c659bc0f989b39a751f687b4afb659ff9039f97784183c81c" + "1c8828aa156151afd1105fb4b725a0129b275285412e5aba66fcf90c16328b8893916b792a84d38393e48fc966940042fc71ae9ba936c4da" + "d86e21ac6a3b5c9f5ba2a284238f4ddcadefe857324bc95246979175cae548771a2a24dba37ed114353a1419e0adf4dd03c2a66c411d6961" + "97e8c11246841d2ebc29e8cee271e70d338b4bb776fca997cd1894751b93f9a10bb16afc8d3e5fcfc841544926600d8030bb21d4aeed3992" + "5b0aca784a35d0fbb3af9f8da4b468150e7811e88f6580a94f94ceb254a075039c7e25241b82379a1691d9913f5321bf89bcd8e4fb1a5e01" + "efca4cd1e662559f849899fbd4386e9224f847594f424aaa5cd0b45e225b3df7ea73e3fa44afbf2c59ac5bfa0ca9a36a08d4460367c0eb5c" + "974e562b47b51adba52b6fbefb3b01b56d865674d5d4e4e8281e27e21a0a851d64565076da964c5f3dcd0fe1bf1c8e755ad28aa1747b6375" + "6e2f7ab0346c8ab5b12c0972da6d71bc4aea4996914bb748abda489567772949dcb047077fbffcdb1f23622f598efb3273de6ac98b29d56e" + "23c2b1f7c9c312b4ffae862ac02f09d03451faa3a4f4325c60e1f9ebc0d370eeb76ced0631c09a782cfa7d207637ea49f915f1f72b4e30f9" + "d87998398c4844057ec0044f80044f45000578b535400040066bed0a2d00010a2d0003145196440dc87cb1f4439577801801fd071b000001" + "01080a7e069e563740bf2b1c441aa7bdce792d8979eabc8613e58684bdfdda40384becadc48d56eb96040eed45d1d1a5dd1024ec07dd33d3" + "8d97f998bee453a2f31737e1ec25ce6e3522616e1a569e1647895f38810f2b95e16ce6eaa3c183ace6dbe89407da75a3e6852bbf3fa407cb" + "aba500f3ddf3ddf271ef79f1c412c8af47f55f8f6678a914c33830fed7e6deb214315fbe119bc20bbaa1182ccd98826a3562bdf6e0a86716" + "156d420b82e71019cea5975e8e5d4c23a4a233195efdaed0ec04ca0aad766143c2cdb5a821eab5645e8d6fa96955c778ba2117c954313906" + "763aa5e8b13c9e48a3b65b0c917de2572b4e32ec6ddb8bd347dd9dbbd4883c28b5709461e83489804cb8f9c9e4a2525b7c189ad55672c168" + "4eb5bdbfb179e5c42c3d4396f217a12606dcee9abb4db749983298f1a0c548523fb35420c4dae9291490dbe04e2bf336b21d3e54e6f08ef2" + "c2502952c1be2cf8d9abcf5777342ed016f9d5ac651908d7ba0821a28771af8be7b431d090096eba3970fe0d3ff6952b6a3034ce00335cbe" + "63b607a57eb6d22a03f65762f796004b228af1e100469dac7189a45f4ceaba12c737a6c2311612905a56d30d1b96efe8b7b58450330f5d2a" + "6ec378d888dc5932108b5b294c451f292f6388d8fd86a237dc1b5886fce9e98309644af1f50d8999ecc7f2327895f5fa5315df4a5f82eeed" + "a80f4e060acb1def8b5531ae88c11c0240ad3280b9f386cbfedfa0300a6dfc6d4d436de4fc050f39a183f8f9d168208054d4cc7a2b52b7fc" + "0d0ccc95c0ea50f6a584dab1ff7a8fe48ae6bd8f976297646c8720b4c24d78c37e8b70ac2f121fd51c872b6cd4ec3c4040876cbc4b6b0d52" + "b3d5d73bdd43aa6a6e64d84e14c66e3e86edd377f8d55127e9daa40e6e85c4caee8ccda06c2eacbefe750985e6f7acdcab9758fac01b4166" + "4745e8e201032034e0b3824711d258be516d91c3179ed14c5947cbb636f19b056e21e3bd21a41070693fc5431ec3f03f1930ff4ddb1b6a56" + "b5876099750b5e33660c88694b8525118b2dfe9dd922e6fe2c0e20c13f8b531f3aac8cf1772cc8b3746f7e43f5469eb5888185d266ce02ba" + "d808e13246922987719a91a25e63dbd9d3f32691cc91c5d5bdbae0e38ba9f12058ff8e97a9d5fd919b070745b93e39d1c68947e90781c151" + "141f179b76b43b6fc3c053df6cbdaaf090c673d68b9b2d6f89f9503332daa2ee24b09f86a612623217ad112d50e9932df0c606f3306c6b23" + "0a476bbbdec1c721fafaf95670f7a0edeb72b89d6f1e6c87a106de10925a4812a4c1cb125bc34cf73e5d13b31424b90b7ae4390c09e3ecfa" + "01815d1e79653b0edb955e9bc01836c47992bc65d6c1b9f2c4c114b22d0237aac167b49e5718ec56ef991c2e7863488c58fea2804ea1bbfd" + "4a81be993751759d4c672edf3326014b7dbd326ef41be0c2da30f526b6c6e222b44a1f038d3df4f8754cc453fd6e05b2a8f5e4fb2d25210e" + "1457cbd9c6169bcf3510797bc868c1048d5d8cd80ad0c078df697df4bf5bf039d8121bcf8f811965ea3e748b86f65450991abf13cec15843" + "54c721d1cbe606c604d8ad30287821c796a0510a79ce1c0d623b125af5d16889e72e4aa5f77e396238ccaffbf34d79729c2d2231374c2783" + "2a38f74e9efca820212031ec5780d3c1d5be044f5fa329b3c3e7ec2794263d32942f04b086b63d50b7d763397718a43d330ff30ce063797d" + "0522db980cbfd500b475e0b9e0637742408846b4f507e0d6084b5a8028202787c31dfc8af66530575ca0d5fe86fe026406248d76f2eef4e3" + "acf3ab6b897352fd0f6950319e7ba6f61edcb1cbd60d829e030befcab3bae493cd840cdfe0f3c5dfdbe48061470328096e717680802d0b0c" + "58451b02bb87e54675c4e519c4d11444057e80045080045045000578b536400040066bec0a2d00010a2d0003145196440dc881f5f4439577" + "801001fd70b500000101080a7e069e563740bf2bccc83d4a4c909762301a98871e0b5cef56d49a2e9334062161cec5eb02f20f8425565692" + "69740f29192b1e7cd69534f0412df2ce692b28345bac8817b5da43a4e2bd8b27f84ca20a80240500c1a854a81414ed198fb13214e8620b5a" + "1f394647afb820ef1b0681d71de7e510918a95cd4b18130de7fcb35d1ff7f3fbc0969af42e7060620184aa225c3516bf4958ee8805564336" + "451852bb04d1ba8f891bf07fd342bab3eca57eba3047e4dd6d682a280dfa4446370e197a1ea6d300dbe46fa1cb5623646d901a37e8d8cfa1" + "fef9be428af2aa4bdcef784fa9d979e47bdd4a6003fe5b7b8b454f49f8ae8459ec8cd58c5b253b404b533ab3b422122898369321be7874aa" + "280fc2289afb6cbd273941b3ac80a8ab9f3cdf97ad96319c916f8f8d1538352f6d36ee66e62b8e443b0a28821176457ffe0930019242c16f" + "bd0c6f3e6af40f06f58e2ceed84b80be7667394fb86b28e33b289e38ad01eed62e9c918d08988532ddf5f8b23c804c390ae063d4fd5d7c71" + "d0f23f3d3ccf80353ff21c7d06a0f94c43c9fbab2c7d8e6999bada2d4500d7071d38294d1104220e507289f1b1efdc728af3ce5b1b8ffaf6" + "3e87441e4910352fc6f37e79c31fd0d2aa351eb3d8a972adffa594ae76332b862e1f2fffd2c8fb225bd3ebe2b3d326cc4aef23498e995c0d" + "488fa679053f8c37e810ac16eaab5c684447fd79b16156a75f8708e02330f529b038b150e2e8d085bc69174f38a6412dc11c8a01b2363bac" + "d1734bdc34d4d9d1a3593a69bbba0b2ddaadd36f02175510c0143e33cc1e0ea16e5d907bd379c60467a52bb69ae1e39f59a61e87ab78df8c" + "f5785f3c60bb73049ee4c18d7c9a1cf3388dd6d14c7d82f85500d11df2b1f8ca7cf710d64d49bd9adfc6328fcbed539161757aaf8f055e73" + "92feb3b6c5054c4ec6dff54ba777ef5af3086b3355f4d3d8d742187555f84ae3c033f5f32e9d631dabdcc3e10a6f18a32bf32fc49bc85851" + "58d68d428da3516d646a75affdc9dc0ed074e488c85b1e50d56083cada66ca3f2bbf699606720b3ac81dd3c1940f19a56bb68a0bded5d1e0" + "b82926d19bf4b34eda76755072c4854f0f454764ee86450a96ba223db87db62c4cabf53db84d1071b6bc201e855ceffcb579d89792063370" + "ee33fb66716bdddd1ee511e7900ce3fe2db4a19512fe41c54fea0f4d04843095682a7d2e59bd6befe46f6d5d6d31b25486b9e2da880a0645" + "7afba9d4eaa911f539c93efbe4a4a995fd70c59212eb5f210af7ececc1060c8315221ec8d18a9c1dcd17a80519928beed32e704bac3c53f7" + "084fa17984341743f3f878b9115c9505d274cd091ad2cb0a9f0d61560e73561759f966f59a89d3bb889468002ee2bd1e4b69131e25e585ab" + "2e0cc0704fdc777d97402b4824f39021596aec0fe9a7e8f1afb72eb1dd767d8c08de2937d3d39e2e7e51ec14f3401f77357e86a4a19437da" + "b5a0f17921cf08f8e5ded1138b1778fa407fa973dee162e4211d4149ff802779e173aace68a7c25b7f92e6a891ca14d9feb53da7d30c6555" + "1dd2d1f7ec1a1bb58242877a09e5902e49f093d008620e589003508e5c89dd5385b4f96ace31a4e53e986ab8b704aa41ebbcb8fb348a6b35" + "5e16446dd10db58bf6ead3a8e3d108bdaddc3d04faf704fd1a5076e6eeef3877d77aaee04c7aa941e92e63df1ba0c9a8347fb3e3ef10fe8c" + "cf610b4b070658ee24f6a10747e1279ecc36adb2f9e87a22787b7844ebb123bb2c83bb63ba943543c73486980bceaf4061f12eea3fcdf649" + "4740fa63e67b2b217c61c265fa242326e884d353e0472439adbb08a14f1fd4bbd5b5afcaae04b9a7ab734e7eaaf838e0f00decca546d7f78" + "89883fe64b147d34f484dceb5069090e460fd54ad6c0599744057e80045180045145000578b537400040066beb0a2d00010a2d0003145196" + "440dc88739f4439577801801fd494100000101080a7e069e563740bf2b98d0933602d916374c32cb9beb41a7e66cdbdc74295b4caf025444" + "1623b8524dac0f1e3ade88322d58c3b2ebb2c1db73eb972dc44735c115d5469e2080f0199f6f90a3f620564e4a6401d2282b235850a18bb8" + "333a7081ae50465024199ec841fe5775494926f5ddfa7c23d6b87927e6d57009fdcd76358c243f01323b1b41344db8d46deaa49afa3a669c" + "11caebcb6f4b314ad9784327fc14a8b417329538060ded327d32f7db6288cb59b665526c18b8367dfdf9648a86bfb64632ba499e099b903c" + "cfd16574966657305f80e9236af622ab6a07a0bb501f92901b0c9c2298c80c355414c7d66a96eb25328e0f602d692e07170ed9068fcfe502" + "b4a52d7baf816c5ec41aebcdd5e5a2bd7197791ffb480b6a485e2cedeeb13e138cdd34d5caf526c6671de7f57466b0ea567f1fa02e8ab6e9" + "9de86173344e95dee0bdb26fb3fdf4647700a8350f124a9afe530af427b82650b6a91c62369c806ed6df86bc4601af80c4181357209e4947" + "9c8e759ca001d2a8801f08466ae1660d81e9a561ac62f04bc8b2da2da7147d8075da6f869212c987515b90f9e9b5b467583c8d43e835aa3b" + "66b48e87adc17dc2468789ec4e1de225832aac02381c041a80c520a65bd20740808df454ac7bcf0b97cbd965ab1ccfb796f4751cae9a96dd" + "d2d3abbbce8f4b9f63732e0cc4d49a10c7159c8dc58c6c5fc25ac5f0878a5214d3532d8d8734848f784fa93bf353a62f29b9a1a932b2f666" + "8b5cf87d2429347876373b8e5772e866436b00b88582e9a936ba22db42c0457d1483574ea8f09f2874dc721acf3ea16a626875ac8d077fda" + "e7971d5fa2f75621d0e747025921a91e57202a4aaab2b5c9571682b515f0506d07046dd35b23b7557d2d03ade7fcb8b0e95489fc5159f0ca" + "010e9bf51d80eebea7506a45dc2d160b45ddf28ce9bac3268841d3dbb646baa63b6d5d7ca027f041295237fdce8c82168b1b02fd193d3087" + "a5aaede64f78b2ba033a0d7e034089554ea191f9dac8c31e907fca2874a8aef671ebc9b584bb1a184daeccbc88810cd754fd072b5b77e1e4" + "8f8f381302d397325a600b5a5564af1db1213f334494b91517080bc459c2308f9e8b14175770d5f74dc36eabf378bedd1ae87b35b130cded" + "1b925c90486bc7c2583f45748abf2cc552e6d1b05516c2bb6c26dbd1f4edf60a18ba8e81e55ed18ea076440a9e23efa5b0b03942649ba230" + "7f8bfb7abb38e143c28330bb9b8236f0a7be67cbda8256608c4e762367491e61a0e6a8b2cebec0124e26b28aefbb1b928e1a383d8ae0b3c5" + "f94a4dd5fc90371df089baaf77c97a1e68a5d3dc8e4d2a4fdc7fe6d69c5f1a52d8227e7c0d16b8b5a3fd52ea342586249ea98e7de0aab783" + "c9442b52c59e4c861ac10f6251fb22369c26d1d37dd71b3024b45a1a1a7af86b46166c2c0f97bb70026c3fc3098d33d10bbd31e218dab37d" + "0edcdce783650ab408efaa75dea3e9cd24a3a37a995261237d49314fb458bc65890a6a83dbd6b584fbac80917edc1a2372f9827acb31651c" + "073cb5d5aa26d7f48fc9de3f4c825e0f0e8aaf3f54f330c330bdbc06c00dbdfd639cb4d0ff87fe07cca1de5800cfaf8e4d39a745500df62f" + "27bedc88a1f198a7834d974b841186122f0b467558d4be0b7dd33c0f7574495a151055bb65a2f2b70b130e6eef2b6d44ae91db655f5603c9" + "2a02e0e2bed77d17b0891552d38be9a75a138a702fc55f2e1c670a7516422deab50718a82a9711db0b9fcc008b37725fffa62042573b9bfc" + "5b83a723b8927126e420e768a048d5ac58c2ba9cce131e503018df0e3f4fb47716ba0c62f6a6979b2dacf3a0bab482eef2832141f9f3762a" + "94029e4278d45f3491582b736c0d2bda187f3c434cb7b9f8755a5892875588515a44057e80045280045245000578b538400040066bea0a2d" + "00010a2d0003145196440dc88c7df4439577801001fd764b00000101080a7e069e563740bf2c4561ea729de6af7c0a79676c7763ba17e7ac" + "768c2951bb9870622e77b4d1e904e0d2663c76acadf4e20ec8ba9b8ba83785c9cc2febfdc8ea3d27e9fe1269b777a670dfdce037f19bccae" + "ce3048b5bb19a900e80928c4b0aeb94cb0f9a38ddb1ce241eec24bed501fceb2e70bf8143d98ad36ae0936a9c5c6eb8edd5f190bb58d5421" + "9a5678852e7ef857f6c19ee1ba30f1a26fa1ab7992969f2be4e1f475b0fa04c824529d121c60113d2f4993ca4c4152c82f98857b1c74343a" + "48731bcd487d4841eab3c0cca90d7ace6ee4eb90dcea844e49158ea5ee26ad7f809591b2b00a6451063ea4fb085792767cb96dec83294499" + "2d4862c6c6ba255713626ab834bc2f1acbd3dee8fbe0a071e55aadfdc392968206f7b0a3e2f0da4d222c3a0e937b1e0155a8b6e5757e8565" + "cbdf08e73be51e4f9df301064b50fadaca86b22d16c258f83a4328fe50ac1232fd777670267d10c69bf5838c02b608b8598aaf48427f6cc0" + "2f1b446d872fbc0fc8a3855620b34e1ed3103730158434e61306d7167f79a11f864667bbf38dc6c45cd17e5b2fd67afa2cf9df200a7fb5bd" + "d3196ef2b0485d71452a766db4c35dc941ceb20f18f88ea85bc65bdd9526ebdb0cb0d69609e42b9029f09384d7657f82f0eb562e0e15029d" + "8344e5a49eeaca422a4f9f65185be693b5dab21bc685379c6d52c740644cfbcd655f4e12a54d7361cc242ca0b752301040708dc7c80b1ae3" + "938a82ac6d60881260c7db7cecbbd5339bf94bf04fb1ca52704b53803d1cdc8ca1571ed5e417db023e67bfde37e519fca95b85fb83a92df8" + "a17bffa8ca0c16c8fa17dbbf4523a1bb31dc377ccf2e91b588aa0c566bdd0b428424c375a49093e3e93a0757f86738ca427597cc731c0b6a" + "52c0b49ffd0cd6fb02083759ffc989fc0963f3140de34acb03c7211335ba04f0cc2463b82d7ef55cab7958f4510eb611dc28324cf531a8a9" + "ce71a44b711f60ecea0e00a1a955a400b528fcf8530450b7f9580b1fab14bb847d4d7b29ad62569d906f3ddc3d1c37b75a5a558b6e84e304" + "e7f9d5e7c9a1a9167ee3b758317f7110e95735809d8ed01189cb47ed3c965e0a29ca1c5de7d30a7c5320658bdae1d0680a4479c5e05c1595" + "4fd6089530a25db3f4a549f92aef6d2362b7c6979577b5242ee6d94ef97565cab74045cee009b57751cd5e5188399611552442d686a0456d" + "bf40a210b1de6b4915c370659b8d3c086a56d1c3dae7a0a067c97598076e3c32e0e0a8993f5e9852221efadab70531e7f97b535dcf1ddd5b" + "c6455b4e256ce96c4c5cb6a44bf73167cbd101c5ed585322d65786ff6eda4f411872357f01458e2762be2b5b68d5221ed0ed359dccfd6cd4" + "30a662af3c12f8bd769ebefc619835f4995c2b679a3cf1253be4678a39198907fb86df8c02f369eaf826954a24565467716f62931ba1487e" + "d8b34bbd1edaaccad6eb35e0d0a53253ba0caaa37b15e10ee13a7193d5f9d4c24f131bf32a2fe971a37a4a32120c636fd327aecba2495f84" + "0fc0e062eb2f4c138114bf484332a4193b68058e09057d31ea92dac20f13395d3242d55a66f749792b77e467dd01a90751d2dfb3c2e4b8a7" + "da21eccfed95bf8feb3189433c06d02b134a8a2db80ce983e89702b8fec3448d7071361a7e5c22bd6035a46c50ab20679cb9f0839ebd0ec2" + "c321b2e24dafc4c117264ae1ce27d4d4711e8568b5ed30db24724ec30fae3986937a6b36477acba89e0096ddb1e61d18863315db078a2cef" + "2e0b632c101035e0804616fa2a8058c0a2970ad7023d0a955c75d01bacdee776191468aed782444b664f31d348abceb76ae6ae6e6fef1b09" + "0adef4e73c7e3e104850931b0c4a3917f23a183cb352782665f7b1408412a0719a7a3d4ebe7db5e66b6744057e80045380045345000578b5" + "39400040066be90a2d00010a2d0003145196440dc891c1f4439577801801fdf02c00000101080a7e069e563740bf2ca6c99f170fcbd78465" + "30e1e5bc62cfe2227c2b02d309a0c23d3883eab7e23aae444300c63d3ddec0075f01bb9ccbde7cf775095335ab1034cebd07f0ff74b7debe" + "3cd1aa343c988025c258acd2b299a119d07045e0e02c0c815171e20e1947faa91779662ffcbe374318637e62b889d34f7b71f2dda4d6ca4c" + "c73e76d9c97fd5bb0f0c6af96f0c307775536af43e7754cb53b2bec2a93b92379f2209592aeedfb2b34702aba85bed9d2cbb22340182a2e8" + "a4a7d7fba881d3607f8509caf7df90fb50a0dd0f1b0d809d993d33ef6e3dff6d06ec0932bada41115230386ea2b0063310d078f035d1ca9e" + "0e1effe47e23938e9417e9e1b28cfd9be2d3c4d082968f035dc9c63063927210337a39d6e4cb2862982a7498c06a2851c753eb7176c3976c" + "fed4a2bff2d14fb838630fe4d1bf59614e940eb8281591f12639a8cf58e04219bf6e3c279d4654a0b5e1517d39ee4d14eab1cc328adb867c" + "769034848ff3386e842890a5b1ce6079c3fdb4d562c0b520dce633a595e55b8b6ae8ad899651ddddcff0dea91c99b1d6ec9f62bafa18a1fa" + "d91e41a7d9bc180b5ba24965b6b7881bfe50fa708d56d8227b381a055a27b8f2507f3c69951d4522d1ee66aad8d2780efe1322ce089acd49" + "75a0a02b93017d0e7701eb66ffd058738321c4ff66e58eb6492ba0dc54d13afd74221963a2fb29dd269af9c9b96b2201c67cb7363cc9d43a" + "38036bee19c870b7e3f63222dcd7040e7dc5735d03fde4cc351d7ca78c73d58c8f1c35688d5ae6e49a0b90e2e16568462d07e49d461cfd74" + "18e820d62ad336947b183b0ccbbac24ad29ff9133b0f7ca01bfa529ae130aca14e9feed6f6c7a41b5248631bd3f3e5778f3cf5a820a51542" + "1b07bdd20e8f20c04d855a28179f2f6e2fe771c14d5bdc16796c09a49f3199d4c5a2751a54cb1fa43945c74149eafcb19df3f40514fdd95f" + "4ea9761242f2425d29fea872a6e8d8ab4a99a46132686a35c15c09dae44bfd975a3e264dc308dd0301eb0d000c4181b6586715a1163394c9" + "2b209fb00dffd9d2054d3363e425eddb2b59d7dc297e6697e2e016358dc2c45a3e6244b1447105724193a3e729ed4d1ec3852410d608dfb3" + "5ac801699fd9b76a1a2564275b1c98a34bd1afb2747b845d93db0cbed28a32a13ba7329051513a8ae501a1cc41f006a8e7558e230c6436b1" + "f5486ca38e985d66ab72d20c989a8bfe0d704dc3bf9f72124248c965a5c4ee42e396c341d14f8a0d98736b8a26b996f2a74c0711113c65c9" + "038ed4c00a4ba2a1685861171b9a711e2e852043e14e7aaa786ebd109b71451438bb782f0c2414a1317950a2b4557d4e276793c5df7cf9ec" + "de627334824c4d324356922ea83aa9b60f5649d304619de16bf97e598ab275cf2c61548eb6a3f83e9745215447525bef4feb034e8c1502cd" + "e19853ecf8e0e91448aa3c2ec1c6885f1957b19ce496311766005822d315a658ae3f4de3bf918c9aa25bed8d61000600c86dc6839949e8f8" + "97a56e2ba80151ff8501679b1838fdd0119619e20f3395d75c391478ca6f558ad5546f3095d655d2d719f1fb5f714e56caca16f331f72e7b" + "d50a63c1b18cdb91f7bbd290fa85e722db810033fb5edcc023dc41369ac31d9c08ea3fce94d86c2fe1720f66f7f1f70295014596da03a738" + "7f59dd7d7c105e3df35a6542485e2bb0b93ed2bd4f97ee0aab8e8c5c77ff4fb6b9f6ff52d7bdf7fbe5bd923966d22892c1791cfcfa4efe05" + "97f9ec6a8692fa096d312801c59eb4b513a03df612673a54c6fbfaab20f1bebcc328bc7efba9ea0c82ae4da35c30dec172c672abb8ee9519" + "4b4ebf48bf972eef490d8550c5841da62d9685c6754d4919a14c02d1b810a0b77faf097c55f82c16000f6adaaad0f393136e0d44057e8004" + "5480045445000578b53a400040066be80a2d00010a2d0003145196440dc89705f4439577801001fdb51900000101080a7e069e563740bf2c" + "977c4f8933bac1635ac1b347db167e97c35e80a54f1f76dded8218d38a91107da54274e31d4c221f21d38412741abe1d35937c17c5379b3e" + "8cdf023d96f0fa9f8170a45b3acb62cdd7e00840d73c8493bb1952af1b9cf0857c0c62d1d3eb8ac0772dc36f95fe6ab57baaf77b797a2e9f" + "c4ef0e52ec1bfdaa1f685659906444b4907bd79d6808fe173887afd04f03741f0d7773f25d5b942a66fd7d00a29f3ace4e9ed48ba71a2ab7" + "6122aa8dd88c4f73cdb4f867f6888bd116f3558f351d0257a07c593a885dd8d3996fc3c29f96f46ba3e4800073c4e7a57d7f9e73cb2298a0" + "d8bd1cc728191748660e175a11914afc13077cd5a3d3231161dce49d57eb5f9bfd163505d35530dda334d578be3ba05e790490c6194f0103" + "4614975733f39560077dae5e3a1ee1192b7a74a9570e2177f2c513c32cef433715ec74f4c2efa30d27d73a8a02210f99ba0c1cf41eef66bb" + "bbcb85cc30a3b9dfb62e82910412cc09827f57fe7067f7359cd6be112807502d6198a4bbe666cc240e7ca45d081dda065e20f0f7feeffa38" + "451252494cddc4f4e05175b0baf8b62f4917cc262b25a6337044cc34ba548afd7651f6740c8329ae05b4821247e3d21b52767e72984427b1" + "ccfbe20270dad0130b9d8dbfc758441668ca5cac2a9400ee4314ae83dc6a9fa5e39a83ba9fade0b74ab9d96db4fffb876ad27d763a195311" + "2c13e9b90f409d59fb190a97036037a6c126cad807a314a433beec6dc46508480621d66aa32093143ce3d015e01ea80af8e0967c64108d67" + "85933d88e5adeb56f7cb06a9777b2636858ac787f28729a1dcab5b49200fc89f82eac81148464184861bf429ca70a308c7072016e573a6fb" + "749ea40487662cde6ba88658f158cfe82889a5f6cf3af5a94a78d2f41eadd224afe358859252fd96bc01997c67534d7b74412cc0f3ba29ca" + "815b0a8c8b5937fecfaaab8add042cfd7097bdff7bfa7673dcfb867c6c1f9bbfe3cb6d03f34aca8b89b28c2a2ba55d620b69b4bf4a43729c" + "a7a28bb67e069df0b9129ca3436aaade7ae366fd5a54389da34f7ae4597c8b718e120b125e0ab0e9f0caf03c28bc48e1d3a16e368640a4f0" + "ce8c5720a698764d46d18c003dafec79d62dc57acea5a2cbffd0bab6e3703a800477ded10efd30c8a13297474fa63157343ce27d4a86a589" + "a31d62056db9ad9b8105550163e6451240dc0f074b8f346a91eef6342cf1131343d971efe0773820a7d98377f7d41f1701249dfddefd4ec5" + "33ceec2432806a4b96a8ccb524c3a9905cdde611b7789a42c78a11c9f90d5ad95858b0a59bbc7c14b33b694e2343876be6fd15a0efba7b91" + "e68dcfc85d11f91df623dd03ebc590a01f6b0cf96b4cdde89c226ae8560fb2f48507fdfe5b3142d8ec217473e1b6bbca534f210a6b24efc9" + "4335fd29bbbe004fe38da90b51eec54b208bf9b5f59ea5d78d58638e89672a65cffd0e972d53c635b178682aa48e1d0ba2684aed1c9594a5" + "1c267c9e4bff57a29f9672db16e7ca30cac6cec476bca4a0ab114e653ed4618d7dfec0580b0d896972c9e0bcfd54ac43f1e53c02eb6cabe7" + "5477b522a5c72da5a033fe744e4c23d6716db95f546e0a3aeaa071a1f126409b7e3625980093c6ab42bcf8ae09ae4cc54618dd12f6c9bc81" + "f6cf0526aa8eac8c69db869a208519e50f2a168f037027b516ea44099877a7911830c738791d75cefb5feee854b7916a009077e18a896d14" + "90c3269ce885ec6d5f0ba5aa3b65366745ffe35250b0ecb2366a05b2213d8b989c5ea099f1295c38b6f2c57c6a4cc8263a575263f25e941c" + "c37b3dce14b9aed9c2bdffb4bb85e7ea25d8d1cfc7ecd4e036c9b8ca042e840e967d06c13ba2c51ca5d81af2233dd8a454de51fd9af665a9" + "3239c04244057e80045580045545000578b53b400040066be70a2d00010a2d0003145196440dc89c49f4439577801801fdd6620000010108" + "0a7e069e563740bf2c59b87a35f5179ea0b6405a660fbe39fa5b6860b4fd22c160944c306e51dc2513ff8f8915a0254817f1481a9c2d7326" + "4e6e95c68d4a516afcd2e048bdb4401bf86bfc1d95186a8b14aaa1584799aabc9cedf9053f2641dab620c1df68231b778afa193c570ccd0f" + "fa04646d65f591f8da32ef4b7d3fe593521352a64d2294cb410bcdafd50035541096cc0fe84f201bdb880dc64401012f481752480b9bc0d8" + "0bd493c2139817b38c408142b55cbbc91056e011a35c58b6059bbcf2c491af60cf27e172b3d45241c96b099b9f81e87dc49fe254f1e0c6fb" + "63a595ddb880509ddba227d3c8089905c3e27577d7a10f6434862027983d1cbc98611809a6374be849518c6dbe54b1006d758d5463b0830e" + "eace4f6a5c9e3a281347c8c9483dc72bdbfa4cfeca130779e13e326948e83a16cebc6441c3831b2acfa2060b45f46994aed443b88718bb15" + "5ac06d99ab70401eefae033c88f3d9c26cafdcf3b7549722b343a4d5ed9f39165daf1d4fa21776c10a4cc11ccf0686e9da723d6dc6437b94" + "70bec396e37d9786b3af22380183b6a955605ae97a596cb09a9f687fb68d1ca117e1efbd6c97ebdb040de0145a6483640ee03148db79d475" + "faeacab5df4cb057c52ffb62a4cfc5414681239118491521dd44ca302a37c541c1de68d1140c0c52f27937232f6ab4d69eddf85d5b807146" + "5407465e68a9de3d943620246325394a7674d5c37e315005bd51ff06ae9ef20063019ea4eb7096c763dc91dd999a169d85d87905734914fa" + "23e6dc366a31af19da68c25e205d0d6cae37c862d9e07af0f63a141304dfa94c4be519ac58440972ae31b59ef15ce46ad2714586d128ee08" + "a5011cab6a30ab74298a5221a321cfbfe9bc0e93de2d84a7de991e970fd2299d83bb529da8b451ad34a8bd5a456d7ee24c9e72d7686e563c" + "7b788858bc4f78e3fd38c1b753e3eb30a9fc77d07eb9fe58967f2b8f77cedb539b769df4f8c271cec4c9062df81fc3b9e5ce64c2765e8259" + "ff73a52ef24f7b161ec8975024f097b36761dec9beba2a7c51ee3471b345ce214a826d323c938d7f9040c08c84e0a2314de473afcc21b068" + "9f40dbd1b570619831cb9dc56cc504d9b2a6f726cea70bdc2ae462ac5c0fe1a3a2e713118ab4ec9fa2b80689386d45ecf3c5266329222f71" + "bb774d4716777e64cc2170de49f422eaf0a437233010ae940cabed94ca1a22298b79d2547df8578db2964cfecb382236a0894dc6b0aabd20" + "6e27280dbb101ef311262cf5f349116e4165ab50e34ce0f027a19217ba8d3e393b9d6521cc43c1ac096fe58a91550857e72e7d8655306f8e" + "6d414834fcbe557952b15ac424fc27648155e9b2c26e6c56c1061567ab8becfc8b54b00abfe800719ad5b92465a2c2fbf0d3cc9ddf3ba00e" + "18e8b20bf8412ab21c3a38ccbfb16fa1216869ac0bb9df188ad687acdf90afe47ede664e1ed2fb22a44ac71f877df095ac44f8de2efb3b71" + "540f129bb9dfd3d3c8a87f190a9feacee84e1cd57671ab59b62985dd659810349932c2782c5c2d9ee5d21698659ba2ddefa0b95f787ad1ca" + "21f5d2f3b9a8485a168d7e12a936a3c08ee784d52c3a209aa5a015b1ed501770c8e2507c35156784b8cf167d3ec092d739df7e970ad14288" + "817c5a58f2af904e3e0173decf92bab601c6ef46f7cac54aea4ae21373e929eaf27ddd78e970a9020bf46491e3cc1cd8de04c7bdd741df31" + "a9c7760c3929a6a62d11fdcd81fe8ab5e81a95a6679d003df8ef310df4eb2edf7ad6d94c01cfc67befc432846f746ae801be801786075b34" + "1d5199fad7c7f58dfc27899fc0dbd483e78c2bb648fedd97f73a9aa29101930a8b6f8e3fe22d2764c60b3d715d931a57a85983809a1a114f" + "64b6fef48d7e537e8047b1363b44057e80045680045645000578b53c400040066be60a2d00010a2d0003145196440dc8a18df44395778010" + "01fd9bbe00000101080a7e069e563740bf2ce06f1248bd16d7296bb3c6a263b82e3845e48f8b154117f1e8a30024850801c815d78d522769" + "40519ce6b96a059fe047fdcd5d3b46bc57c66e5b4c919e6cc13dc445546ebb4b547d255c45acf05bef2db27e7a0225fffe46af19a9369362" + "5b81f5a686d9edaa0a23061b5507b9203df4bec9e466be42814712f941decff0f8bd443045543faaccd84c171b4f99ed218cbbd8c4f22b3f" + "cbf12152fcc7e6b9b2735aab2af590edc313bf6c3a2902050115337133632a0a0bed69b76c1a04af38868dd18f5a1818e042a7f202fc4405" + "c54885b0258e3cd768076b65d5d67c9dceaabd9488e997e0307469c95d9146dfe9ba37a4096f3115be6343c532a850e9586b9274e50827ea" + "d2756e90577c490d4852841e5306cdb2b110cb1a5e34ae8702b8336b5434949f00ab7df67d47bb1425f3c66e76c83d8a0fe06120d9d5ce5d" + "3b0d4a4f3969057b6004389ceff043c01f215b4dc3094c5a350440f82c4ec6dd2c8ae80f183cfa0f6c6f7b66f2c47474bc55d6561efdaf5d" + "a79116d4cd17816aed1d2676d3a59e23ef01bbc3f8ccacd9285cb088298f184c235959dc7da033dd7651d04decf58e4ae85dd79784b9f54e" + "bc1d075aaddcd1fa8eec4181b877c972fbbe6e59400e0f8ced3c18f9a3713cc8b98e663ca443f9f520265e26c3c30bf8cf6a06a2f21c7a9a" + "b91398b5781578f7a953ffe551574b87c6d396c48349785e4fa4a7b9e5f9986781c8ec74305dd08b61595fb05de25a9bcf91cf2f80d31c29" + "088861b5fcc599b937e7c1ffd8bba29a01f730b3ef0337ea94c963dbf1bdf32937a0095dd4e9f94c721ac4d310bbe90755bfd2f3ccf1e685" + "77280034f1ccef5a3d1148712910904a5691df56d66d4a59412cbe6c9620644444c07454488087a887b78df14261d59c638c66c38c44b309" + "aa5f09583f10b4fdcf1bbaf10383d810a077d88a1bfe062193f9da8ce7534c896a77de68428745df36b1237b8bae3c9d48ed06109adaac89" + "280947a82a8826e6092b975ddd4f0a6f181c007c99ed4e78a8149adf267306d98a8242910d8b388ff3d65f169b487c33e08a3adef0974097" + "69e8530b00585adc9c60c3c69b66b1281f409f22fad035ecaa6e5aca85cb4564cf6c60a80eefa80cade210fa32178b61d60e3c6c095574eb" + "5950d7c068b18914f65a1ae13b49ac39551eeb4899c427306d26331648b7f131db1e27170d5e5851710d8e0fe992603e0d7fcad95964b1c0" + "46d947df874045ae03216b3d26f5bf5446534f6ac0c0609b960ca1a1cf1be377a86d39d8f6a367673c29cf6285bc44ac6583ead8dc321122" + "9b077da4cb49ab43f100ebd5f7297923b4bf63479345ce2d93458597f52e767106837fa433ab846ffc36ab333d97c0cd63e9677c22d4e49e" + "5e90af290bc3e08663473d8b0daa54e0120cc8039c56bdf76e4a4e143ee4947c6c246e4f7e2768c720bc824bc1de3112f8f84b58fa388227" + "1c9ba3ac7de3a80b94c9dbddb9bb0948f862424509382c9a7af37b4b2dc6a14b364d3674141b789e8277e7a95b5b9d9417f8d82eea297c58" + "42321eab91d36ac42d62aac23417ff08deebc5399f01cccf29f83b2756cb7d3fc2960101143ad7daa190155ffe0b93e943bb3fb4bbd7626b" + "ddfb66d6d2bffe41a7cc350d48f7136ffde25487710bea1e31c2fb7f2614d63e12e0b80a9cc3f6b0e73627517deae9e203a94166d6f6c877" + "e5cb8b6f16de5d2d4f93647ae04b063b5a0b0d18b72fd93a0325afdf3f0888c30d48f06ef9f0a35411f0abc2751ee1cf674517f39364a353" + "3cd276d0379b05ec0da16eb552088e3cb484a96aa9fa43abe2c63ea100cfc074be50a3ec567d08112d952cfe5c71d4d5cb6b1c6f7f6a0aa8" + "27cd90a2c460d894f980016e22f73fe2000dcc7b479b44057e80045780045745000578b53d400040066be50a2d00010a2d0003145196440d" + "c8a6d1f4439577801801fdaf2800000101080a7e069e563740bf2cec3ef95a142a919ff39936cecfc079857d2a3def17af3b7f37e1e05402" + "87c860d13f76e896cabdc26904bfb47c19ab719a081397abb83ff61efe8c7e10abbee769cea7d4fec0bdd0ce9d59ebbd6c9c6ac24dbd12aa" + "1d28c1755abe40aa9ea0c27c341fcd6b19eb0536bd8b7c9c434697bcc0d42ffb97fd66edfd815375aeafe6570520b99806ccc3b154bf2014" + "4873daf12849942b08a4de7e5e376ebc318092d5e0b1fd1dd668a2e179d95c7cf2418dd1a780320c1fa592e7aae61bbfd6810e31d88d64cc" + "809c8c4fed6fa3ad6cc73b7d9c99e233543803802823f92148f55a2ef2018bf559671a579bca0dccf61ee5a84303af875c7dde89fe1dec89" + "49fbdec1794cc215253c5e6c3d09dbe9c9f4a3767fe9b0d53d0d9e8fcf40e4970b733a23b26a22b912613a593ac8a9f07be52c4c3ead3a50" + "ef1d2fad02829fbdc162fe34a48910b170048306e590eeca549517f733dfbcf14ce7fe3734a7bb5b85977e7a2b559111ac6ebfa3355951b6" + "8eb93ce88b9e5f4c059e649d949b3942c836525765e0a104d47cdf72fa64f0866bdba6c0719b490a9e1adaca90f6125050d97b1bf96d2ee8" + "4d6b4470a03c9d528cf57fa659cdffb539a260f38e91f2317fd8c50dc7948a9a524be34da3b3bfcb06b7df0be4483386f9fb1b2578fe0562" + "c5b99fffcb2f88d143d5920d984cd8e61ae9c1d328883ebf2aebd5b8552ba459ae2a54814ea79a78c20317d940c074f8b1fdcf4577b4a290" + "3b4118f38f1a1967971f31a992f1918af007c087877a93043b86f05542c79eca59b4deee91c521fb695ee99b902af085d1a07b19a6cd2d3c" + "c873e9ca0ae54eaa83949819884684e2b59040b3bd24d4ad2137310a36fff470cd0092a2049382c8e8420dd8c159a4c5142eccf6160de7cf" + "225ff5088b6ce0fe89dec6b2891e743a5b411b52dd2780f67f61174c53bdd695579d975ee17c508ff20ba6bf09121822e3fe9ca9e853fe81" + "88400380fa0c4ed6233a68630b80cee749077d08ce8bdd6d51dc59950c5c5bd59ece66abc935965c7ebc5225c7cb6fa23e07beb2d8272cc1" + "0a69f8f8c8d49a89134a6e9dafb5936cad0304f1e5c8e47cc6a1846af22372bbbe973793b1f56c6158de78eaed60cc9a985b18eee85c332e" + "a24d4ba063d78927b9a2250b535e304bd33846b553916c11ec3884a81220903e16b126195417c09a0bf7d2a91b57fd50209a4fe93f1ef5f5" + "8a08b4d1541ef7078d8482965bc4a875eb71e1ef56c8a623225e71700a139ba51366e6e58a3e0fa132ae8547f56ab1f245d8b054d9f4d2a4" + "62f4835b7215e91d4291a76cbf3a97baf8d46776c8ccd137516b251d13aa778bfee34702cd299b71c76f95e3c909b3d38dc4b9953f1b7f08" + "93edd41e0738f7353579871aa30f659995b6e698686261b84aa3bfebb8070efccd5e82d3c2179f03f328dcc9e58af02cdb2e060282b26014" + "d4fdbcbb4f77684118058e412890618b245b52841aa28a0c7d44cf2871706bf9a56a41db5fff9e57b0e45a8e0494fc9049c36bc605c36ad4" + "a3806cb9903409ab38fcc3fa6ee61df69d0daf98c62c8c3a380b6a4033a5fbb257e34fa363f6776e44492f6e87ff20abc82cb959bed91941" + "87767d39ea5bf8c25e93007c48f18fc4ccbc8faca025759abab6be27fb8e8e22d294c60592b5906dd96ef8ffaddbe1eac1477e37be9425cb" + "dd9bd17b17ba70de631ea09f6d9b27245a81b8dd9cd85c064d1832166ab182adb6fb8115d5d9a341fa863e92d2b89ff2e9464723b34bafba" + "9492eef498855ac486e4bab1b9ab69051ccc6fac3a47f16b64a6b43105367e2150603e395665666a4774077d4b7f7947b29092ee01bc48e4" + "d668ec61312d2299924197c3b6e92b025c54e6feeaefa0f60698e9157f546b4403c690045880045845000578b53e400040066be40a2d0001" + "0a2d0003145196440dc8ac15f4439577801001fd38e700000101080a7e069e563740bf2c12aef992cbb4a70140b8edba1ca001962ad375f2" + "5293727698804fd44d7df7e5c7c780b562d06167cd4bf3f5f9d7a42f321083e88e6389534bf002e81ee1191f6ac48538119c2c0671161acc" + "b440f4533febe253f5a1254b7efd5b5e19638efb3565c81606590523e338bd2ca479408dbab1af0a8c1261472e59290f2035885f061c03ef" + "9404aea3922bcfe439e132079ebf026d5bda87c5bd6685b9c30bb9543ede8a537f47627d250f7f92c4b3c3511e5bc1e460784bb79f30205e" + "8e9cfb28b5d386433851d9cd6bbfa2a769ddcfba784049748c23a97b9dd9b0102a688bbe2ca85d40f24629cf65d35ba2d9ee8c5c3723663a" + "a63460364a767c2fb928778f39b03716db7905c6822c07bc2f26952a20356e2611088ef6eaf21e19c767f2347eb3a7ad8d761542ca72374e" + "3fb10b0b83fc07aa6b75641b48aa2bbbcd48d422fe0d215b4aadd43a6cfedf3f6ecc83a3df3726ab43703d651b077fac0e959c1a008f8a98" + "7a3008e9244c90564af123662104f0f39e1ad51e304dbeeeb0b6d59ce60d2a4eef74c19ea89b75b559d4c6a24b31cf4816b19b3f0edb77c2" + "7c93c667f7f46c8b4153664cb9638dfe71244cd555cff202313a517083312245ad0653a8387e63e844020801fdddfbe7e63901b3d75f82a8" + "2c1bfb601064db01a5ac2681d35a7797ed44c07fa6b8d782fb9726dea13e7a3bcc9c0331acd67e43e6cd6aab62c511a365c4fd2e67c2bd50" + "03067f098d4cc7f16c923859b5ef1e1153176b72eca753baa73bc91adbd85a1aeec61bb63ad72c62992d1a78a87f19bfd7abaed7040e4e8d" + "21e8ac08008d8914e55cf16977b7a9f84b10e7f29d48404b6e87c111ef5e415c99fdc72fbdeabdd9baad9efe1e05d33ccfb1643c43e48ae9" + "ed7b179693b4f1a270b2a1a6e5e572336160579453d7bef82855b27dc4a26964c9898fbcc2dd26b72019d86b4b26ee27c1a617cff355d026" + "b620b47ba9fefa47b9cbbc7fc97c17ae9713dffaad490e42f40093106a92df63200253682f2e32f1a7fcb7bbe6ef37abe0fc5275b3d99b9d" + "dd97149c9b466d622b7a641f5a8916dc81cf160f8938185057a770ef7b20de955e30fa904ddc828b97f7e4ad111462dd9bb9b724f8b29d42" + "c94a12d6ad43edf2697ffa4a62c12ab00378912b106efd2c50e8c46a3a1f8b682c11f8702ce1591e27042413e38c340def44b68a94a4e761" + "beb25106cf2e60c0aa9f8c0f745bdc1ecb8e9bfe71e9b4846bbea0dc5d2d982d17176c3119432b90e3a8f48195d21a06" + + ); + + int crnti = 0x01011; + int ue_id = 2; + int harqid = 0; + int tti = 10; + srsran::mac_nr_context_info context = {}; + context.radioType = srsran::PCAP_FDD_RADIO; + context.direction = srsran::PCAP_DIRECTION_DOWNLINK; + context.rntiType = srsran::PCAP_C_RNTI; + context.rnti = crnti; + context.ueid = ue_id; + context.harqid = harqid; + context.system_frame_number = tti / 10; + context.sub_frame_number = tti % 10; + for (uint32_t i = 0; i < num_pdus; i++) { + pcap->push_pdu(context, tv.copy()); + } + + std::cout << "Finished thread " << std::this_thread::get_id() << "\n"; +} diff --git a/tests/unittests/pdcp/CMakeLists.txt b/tests/unittests/pdcp/CMakeLists.txt index 6e44a9fa87..375eb2fd5e 100644 --- a/tests/unittests/pdcp/CMakeLists.txt +++ b/tests/unittests/pdcp/CMakeLists.txt @@ -25,6 +25,11 @@ target_link_libraries(pdcp_tx_test srsran_pdcp srsran_security srsran_support sr target_include_directories(pdcp_tx_test PRIVATE ${CMAKE_SOURCE_DIR}) gtest_discover_tests(pdcp_tx_test) +add_executable(pdcp_tx_empty_pool_test pdcp_tx_empty_pool_test.cpp) +target_link_libraries(pdcp_tx_empty_pool_test srsran_pdcp srsran_security srsran_support srslog gtest gtest_main) +target_include_directories(pdcp_tx_empty_pool_test PRIVATE ${CMAKE_SOURCE_DIR}) +gtest_discover_tests(pdcp_tx_empty_pool_test) + add_executable(pdcp_rx_test pdcp_rx_test.cpp) target_link_libraries(pdcp_rx_test srsran_pdcp srsran_security srsran_support srslog gtest gtest_main) target_include_directories(pdcp_rx_test PRIVATE ${CMAKE_SOURCE_DIR}) diff --git a/tests/unittests/pdcp/pdcp_tx_empty_pool_test.cpp b/tests/unittests/pdcp/pdcp_tx_empty_pool_test.cpp new file mode 100644 index 0000000000..caa36efba2 --- /dev/null +++ b/tests/unittests/pdcp/pdcp_tx_empty_pool_test.cpp @@ -0,0 +1,102 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "pdcp_test_vectors.h" +#include "pdcp_tx_test.h" +#include "srsran/pdcp/pdcp_config.h" +#include +#include + +using namespace srsran; + +constexpr uint32_t pool_size = 32; + +/// Fixture class for PDCP TX tests +/// It requires TEST_P() and INSTANTIATE_TEST_SUITE_P() to create/spawn tests for each supported SN size +class pdcp_tx_empty_pool_test : public pdcp_tx_test_helper, + public ::testing::Test, + public ::testing::WithParamInterface +{ +protected: + void SetUp() override + { + // init test's logger + srslog::init(); + logger.set_level(srslog::basic_levels::debug); + + // init RLC logger + srslog::fetch_basic_logger("PDCP", false).set_level(srslog::basic_levels::debug); + srslog::fetch_basic_logger("PDCP", false).set_hex_dump_max_size(100); + } + + void TearDown() override + { + // flush logger after each test + srslog::flush(); + } +}; + +/// Test empty pool handling +TEST_P(pdcp_tx_empty_pool_test, empty_pool) +{ + init(GetParam()); + uint32_t n_sdus = pool_size + 1; + auto test_empty_pool = [this, n_sdus](uint32_t tx_next) { + // Set state of PDCP entiy + pdcp_tx_state st = {tx_next, tx_next}; + pdcp_tx->set_state(st); + pdcp_tx->configure_security(sec_cfg); + + // Write first SDU + for (uint32_t i = 0; i < n_sdus; i++) { + byte_buffer sdu = {sdu1}; + pdcp_tx->handle_sdu(std::move(sdu)); + } + // check nof max_count reached and max protocol failures. + ASSERT_NE(test_frame.pdu_queue.size(), n_sdus); + ASSERT_GE(test_frame.nof_protocol_failure, 1); + }; + + test_empty_pool(0); +} + +/////////////////////////////////////////////////////////////////// +// Finally, instantiate all testcases for each supported SN size // +/////////////////////////////////////////////////////////////////// +std::string test_param_info_to_string(const ::testing::TestParamInfo& info) +{ + fmt::memory_buffer buffer; + fmt::format_to(buffer, "{}bit", pdcp_sn_size_to_uint(info.param)); + return fmt::to_string(buffer); +} + +INSTANTIATE_TEST_SUITE_P(pdcp_tx_empty_pool_test_all_sn_sizes, + pdcp_tx_empty_pool_test, + ::testing::Values(pdcp_sn_size::size12bits, pdcp_sn_size::size18bits), + test_param_info_to_string); + +int main(int argc, char** argv) +{ + init_byte_buffer_segment_pool(pool_size); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/unittests/pdcp/pdcp_tx_test.cpp b/tests/unittests/pdcp/pdcp_tx_test.cpp index 21c041bf53..57b8433ee5 100644 --- a/tests/unittests/pdcp/pdcp_tx_test.cpp +++ b/tests/unittests/pdcp/pdcp_tx_test.cpp @@ -48,7 +48,7 @@ TEST_P(pdcp_tx_test, sn_pack) // Pack header byte_buffer buf = {}; - pdcp_tx->write_data_pdu_header(buf, hdr); + ASSERT_TRUE(pdcp_tx->write_data_pdu_header(buf, hdr)); // Get expected PDU header byte_buffer exp_pdu; get_expected_pdu(sn, exp_pdu); @@ -115,7 +115,7 @@ TEST_P(pdcp_tx_test, pdu_gen) } } -/// \brief Test correct generation of PDCP PDUs +/// \brief Test correct stalling of PDCP if RLC SDU queue is full TEST_P(pdcp_tx_test, pdu_stall) { init(GetParam(), pdcp_rb_type::drb, pdcp_rlc_mode::am, pdcp_discard_timer::infinity); @@ -129,8 +129,12 @@ TEST_P(pdcp_tx_test, pdu_stall) pdcp_tx->set_integrity_protection(security::integrity_enabled::on); pdcp_tx->set_ciphering(security::ciphering_enabled::on); + uint32_t sdu_queue_size = 4096; + uint32_t window_size = pdcp_window_size(sn_size); + uint32_t stall = std::min(sdu_queue_size, window_size - 1); // nof SDUs before stalling + // Write SDU - for (uint32_t count = tx_next; count < tx_next + 1024; ++count) { + for (uint32_t count = tx_next; count < tx_next + stall; ++count) { byte_buffer sdu = {sdu1}; pdcp_tx->handle_sdu(std::move(sdu)); @@ -149,7 +153,7 @@ TEST_P(pdcp_tx_test, pdu_stall) } { // Notify transmission of all PDUs - pdcp_tx->handle_transmit_notification(pdcp_compute_sn(tx_next + 1024, GetParam())); + pdcp_tx->handle_transmit_notification(pdcp_compute_sn(tx_next + stall, GetParam())); // Write an SDU and SDU should be dropped byte_buffer sdu = {sdu1}; @@ -300,6 +304,99 @@ TEST_P(pdcp_tx_test, discard_timer_and_stop) } } +/// \brief Test correct generation of PDCP PDUs +TEST_P(pdcp_tx_test, pdu_stall_with_discard) +{ + init(GetParam(), pdcp_rb_type::drb, pdcp_rlc_mode::am); + + auto test_pdu_gen = [this](uint32_t tx_next) { + srsran::test_delimit_logger delimiter("TX PDU stall. SN_SIZE={} COUNT={}", sn_size, tx_next); + // Set state of PDCP entiy + pdcp_tx_state st = {tx_next, tx_next}; + pdcp_tx->set_state(st); + pdcp_tx->configure_security(sec_cfg); + pdcp_tx->set_integrity_protection(security::integrity_enabled::on); + pdcp_tx->set_ciphering(security::ciphering_enabled::on); + + uint32_t sdu_queue_size = 4096; + uint32_t window_size = pdcp_window_size(sn_size); + uint32_t stall = std::min(sdu_queue_size, window_size - 1); // nof SDUs before stalling + + // Write SDU + for (uint32_t count = tx_next; count < tx_next + stall; ++count) { + byte_buffer sdu = {sdu1}; + pdcp_tx->handle_sdu(std::move(sdu)); + + // Get generated PDU + ASSERT_EQ(test_frame.pdu_queue.size(), 1); + pdcp_tx_pdu pdu = std::move(test_frame.pdu_queue.front()); + test_frame.pdu_queue.pop(); + } + { + // Write an SDU and SDU should be dropped + byte_buffer sdu = {sdu1}; + pdcp_tx->handle_sdu(std::move(sdu)); + + // Get generated PDU + ASSERT_EQ(test_frame.pdu_queue.size(), 0); + } + + // Let timers expire + for (int i = 0; i < 10; i++) { + timers.tick(); + worker.run_pending_tasks(); + } + + // Check that we can write PDUs again + for (uint32_t count = tx_next + stall; count < tx_next + 2 * stall; ++count) { + byte_buffer sdu = {sdu1}; + pdcp_tx->handle_sdu(std::move(sdu)); + + // Get generated PDU + ASSERT_EQ(test_frame.pdu_queue.size(), 1); + pdcp_tx_pdu pdu = std::move(test_frame.pdu_queue.front()); + test_frame.pdu_queue.pop(); + } + { + // Write an SDU and SDU should be dropped + byte_buffer sdu = {sdu1}; + pdcp_tx->handle_sdu(std::move(sdu)); + + // Get generated PDU + ASSERT_EQ(test_frame.pdu_queue.size(), 0); + } + { + // Notify transmission of all PDUs + pdcp_tx->handle_transmit_notification(pdcp_compute_sn(tx_next + 2 * stall, GetParam())); + + // Write an SDU and SDU should be dropped + byte_buffer sdu = {sdu1}; + pdcp_tx->handle_sdu(std::move(sdu)); + + // Get generated PDU + ASSERT_EQ(test_frame.pdu_queue.size(), 1); + test_frame.pdu_queue.pop(); + } + // Let timers expire + for (int i = 0; i < 10; i++) { + timers.tick(); + worker.run_pending_tasks(); + } + }; + + if (config.sn_size == pdcp_sn_size::size12bits) { + test_pdu_gen(0); + test_pdu_gen(2048); + test_pdu_gen(4095); + test_pdu_gen(4096); + } else if (config.sn_size == pdcp_sn_size::size18bits) { + test_pdu_gen(0); + test_pdu_gen(131072); + test_pdu_gen(262144); + } else { + FAIL(); + } +} /// Test COUNT wrap-around protection systems TEST_P(pdcp_tx_test, count_wraparound) { @@ -336,6 +433,7 @@ TEST_P(pdcp_tx_test, count_wraparound) FAIL(); } } + /////////////////////////////////////////////////////////////////// // Finally, instantiate all testcases for each supported SN size // /////////////////////////////////////////////////////////////////// diff --git a/tests/unittests/pdcp/pdcp_tx_test_helpers.h b/tests/unittests/pdcp/pdcp_tx_test_helpers.h index 96a3f60ecb..ee6ad64056 100644 --- a/tests/unittests/pdcp/pdcp_tx_test_helpers.h +++ b/tests/unittests/pdcp/pdcp_tx_test_helpers.h @@ -151,7 +151,7 @@ class pdcp_tx_test_helper uint32_t mac_hdr_len = 4; pdcp_tx_config config = {}; timer_manager timers; - manual_task_worker worker{64}; + manual_task_worker worker{4098}; pdcp_tx_test_frame test_frame = {}; // 12 bit test PDUs diff --git a/tests/unittests/phy/support/resource_grid_test.cpp b/tests/unittests/phy/support/resource_grid_test.cpp index df493e5090..3ef469cba4 100644 --- a/tests/unittests/phy/support/resource_grid_test.cpp +++ b/tests/unittests/phy/support/resource_grid_test.cpp @@ -382,7 +382,7 @@ void test_consecutive(unsigned nof_ports, unsigned nof_symbols, unsigned nof_sub int main() { // Iterates over the possible number of ports - for (unsigned nof_ports : {1, 2, 4, 8, 16}) { + for (unsigned nof_ports : {1, 2, 4}) { // Iterate over the posisble number of symbols per slot for (unsigned nof_symbols : {14}) { // Iterate over a symbolic number of subcarriers diff --git a/tests/unittests/phy/support/resource_grid_test_doubles.h b/tests/unittests/phy/support/resource_grid_test_doubles.h index bc2088e2b7..a519ef09b9 100644 --- a/tests/unittests/phy/support/resource_grid_test_doubles.h +++ b/tests/unittests/phy/support/resource_grid_test_doubles.h @@ -35,6 +35,7 @@ #include "srsran/support/srsran_assert.h" #include "srsran/support/srsran_test.h" #include +#include #include #include @@ -82,17 +83,20 @@ class resource_grid_writer_spy : public resource_grid_writer // See interface for documentation. void put(unsigned port, span coordinates, span symbols) override { + std::unique_lock lock(entries_mutex); ++count; const cf_t* symbol_ptr = symbols.begin(); for (const resource_grid_coordinate& coordinate : coordinates) { put(port, coordinate.symbol, coordinate.subcarrier, *(symbol_ptr++)); } + fmt::print("entries.size()={}\n", entries.size()); } // See interface for documentation. span put(unsigned port, unsigned l, unsigned k_init, span mask, span symbols) override { + std::unique_lock lock(entries_mutex); TESTASSERT(k_init + mask.size() <= max_prb * NRE, "The mask staring at {} for {} subcarriers exceeds the resource grid bandwidth (max {}).", k_init, @@ -117,6 +121,7 @@ class resource_grid_writer_spy : public resource_grid_writer const bounded_bitset& mask, span symbols) override { + std::unique_lock lock(entries_mutex); ++count; unsigned i_symb = 0; for (unsigned k = 0; k != mask.size(); ++k) { @@ -140,6 +145,7 @@ class resource_grid_writer_spy : public resource_grid_writer // See interface for documentation. void put(unsigned port, unsigned l, unsigned k_init, span symbols) override { + std::unique_lock lock(entries_mutex); ++count; for (unsigned i = 0; i != symbols.size(); ++i) { put(port, l, k_init + i, symbols[i]); @@ -221,6 +227,9 @@ class resource_grid_writer_spy : public resource_grid_writer /// Stores the resource grid written entries. std::map entries; + /// Protects concurrent write to entries. + std::mutex entries_mutex; + /// Counts the number of times a \c put method is called. unsigned count = 0; diff --git a/tests/unittests/phy/upper/channel_coding/ldpc/ldpc_enc_dec_test.cpp b/tests/unittests/phy/upper/channel_coding/ldpc/ldpc_enc_dec_test.cpp index d0e6cc9881..00015a65db 100644 --- a/tests/unittests/phy/upper/channel_coding/ldpc/ldpc_enc_dec_test.cpp +++ b/tests/unittests/phy/upper/channel_coding/ldpc/ldpc_enc_dec_test.cpp @@ -41,6 +41,17 @@ std::ostream& operator<<(std::ostream& os, const test_case_t& tc) { return os << fmt::format("BG{}, LS{}", tc.bg, tc.ls); } + +std::ostream& operator<<(std::ostream& os, span data) +{ + return os << fmt::format("{}", data); +} + +bool operator==(span lhs, span rhs) +{ + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} + } // namespace srsran namespace { @@ -121,6 +132,22 @@ class LDPCEncDecFixture : public ::testing::TestWithParam messages = test_data.messages.read(); codeblocks = test_data.codeblocks.read(); + // Remove filler bits from messages. + std::transform(messages.begin(), messages.end(), messages.begin(), [](uint8_t bit) { + if (bit == ldpc::FILLER_BIT) { + return uint8_t{0}; + } + return bit; + }); + + // Remove filler bits from codeblocks. + std::transform(codeblocks.begin(), codeblocks.end(), codeblocks.begin(), [](uint8_t bit) { + if (bit == ldpc::FILLER_BIT) { + return uint8_t{0}; + } + return bit; + }); + nof_messages = test_data.nof_messages; // Encoder/decoder configurations. @@ -234,14 +261,25 @@ TEST_P(LDPCEncDecFixture, LDPCEncTest) span cblock_i = span(codeblocks).subspan(used_cblck_bits, max_cb_length); used_cblck_bits += max_cb_length; + // Pack input message. + dynamic_bit_buffer message_packed(msg_length); + srsvec::bit_pack(message_packed, msg_i); + // check several shortened codeblocks. constexpr unsigned NOF_STEPS = 3; const std::vector length_steps = create_range(min_cb_length, max_cb_length, NOF_STEPS); for (const unsigned length : length_steps) { + // Select expected encoded data. + span expected_encoded = cblock_i.first(length); + // Check the encoder. std::vector encoded(length); - encoder_test->encode(encoded, msg_i, cfg_enc); - ASSERT_TRUE(std::equal(encoded.begin(), encoded.end(), cblock_i.begin())) << "Wrong codeblock."; + dynamic_bit_buffer encoded_packed(length); + + encoder_test->encode(encoded_packed, message_packed, cfg_enc); + + srsvec::bit_unpack(encoded, encoded_packed); + ASSERT_EQ(span(encoded), span(expected_encoded)) << "Wrong codeblock."; } } } diff --git a/tests/unittests/phy/upper/channel_coding/ldpc/ldpc_rm_test.cpp b/tests/unittests/phy/upper/channel_coding/ldpc/ldpc_rm_test.cpp index e1577b9a9a..611c4b9f72 100644 --- a/tests/unittests/phy/upper/channel_coding/ldpc/ldpc_rm_test.cpp +++ b/tests/unittests/phy/upper/channel_coding/ldpc/ldpc_rm_test.cpp @@ -161,7 +161,10 @@ TEST_P(LDPCRateMatchingFixture, LDPCRateMatchingTest) rm_cfg.tb_common.Nref = n_ref; rm_cfg.cb_specific.nof_filler_bits = nof_filler_bits; - matcher->rate_match(matched_packed, codeblock, rm_cfg); + dynamic_bit_buffer codeblock_packed(codeblock.size()); + srsvec::bit_pack(codeblock_packed, codeblock); + + matcher->rate_match(matched_packed, codeblock_packed, rm_cfg); // Unpack rate matched. srsvec::bit_unpack(matched, matched_packed); @@ -191,9 +194,13 @@ TEST_P(LDPCRateMatchingFixture, LDPCRateMatchingTest) std::vector hard(dematched.size()); std::transform(dematched.cbegin(), dematched.cend(), hard.begin(), llrs_to_bit); + // Pack hard bits. + dynamic_bit_buffer hard_packed(dematched.size()); + srsvec::bit_pack(hard_packed, hard); + // Now, apply the rate matcher and compare results. static_bit_buffer matched_packed2(rm_length); - matcher->rate_match(matched_packed2, hard, rm_cfg); + matcher->rate_match(matched_packed2, hard_packed, rm_cfg); EXPECT_EQ(matched_packed, matched_packed2) << "Wrong rate dematching."; } diff --git a/tests/unittests/phy/upper/channel_processors/pdcch_modulator_test.cpp b/tests/unittests/phy/upper/channel_processors/pdcch_modulator_test.cpp index beb4ca4c46..fa0af4d319 100644 --- a/tests/unittests/phy/upper/channel_processors/pdcch_modulator_test.cpp +++ b/tests/unittests/phy/upper/channel_processors/pdcch_modulator_test.cpp @@ -45,13 +45,13 @@ int main() for (const test_case_t& test_case : pdcch_modulator_test_data) { int prb_idx_high = test_case.config.rb_mask.find_highest(); TESTASSERT(prb_idx_high > 1); - unsigned max_prb = static_cast(prb_idx_high + 1); - unsigned max_symb = test_case.config.start_symbol_index + test_case.config.duration; + unsigned max_prb = static_cast(prb_idx_high + 1); + unsigned max_symb = test_case.config.start_symbol_index + test_case.config.duration; + unsigned max_ports = test_case.config.precoding.get_nof_ports(); // Prepare resource grid and resource grid mapper spies. - resource_grid_writer_spy grid(MAX_PORTS, max_symb, max_prb); - std::unique_ptr mapper = - create_resource_grid_mapper(MAX_PORTS, max_symb, NRE * max_prb, grid); + resource_grid_writer_spy grid(max_ports, max_symb, max_prb); + std::unique_ptr mapper = create_resource_grid_mapper(max_ports, NRE * max_prb, grid); // Load input codeword from a testvector const std::vector test_codeword = test_case.data.read(); diff --git a/tests/unittests/phy/upper/channel_processors/pdcch_processor_vectortest.cpp b/tests/unittests/phy/upper/channel_processors/pdcch_processor_vectortest.cpp index c76150d18c..7d8704be40 100644 --- a/tests/unittests/phy/upper/channel_processors/pdcch_processor_vectortest.cpp +++ b/tests/unittests/phy/upper/channel_processors/pdcch_processor_vectortest.cpp @@ -96,14 +96,15 @@ TEST_P(PdcchProcessorFixture, FromVector) const test_case_t& test_case = GetParam(); - unsigned max_prb = MAX_RB; - unsigned max_symb = test_case.config.coreset.start_symbol_index + test_case.config.coreset.duration; + unsigned max_prb = MAX_RB; + unsigned max_symb = test_case.config.coreset.start_symbol_index + test_case.config.coreset.duration; + unsigned max_ports = test_case.config.dci.precoding.get_nof_ports(); ASSERT_TRUE(validator->is_valid(test_case.config)); // Prepare resource grid and resource grid mapper spies. - resource_grid_writer_spy grid(MAX_PORTS, max_symb, max_prb); - std::unique_ptr mapper = create_resource_grid_mapper(MAX_PORTS, max_symb, NRE * max_prb, grid); + resource_grid_writer_spy grid(max_ports, max_symb, max_prb); + std::unique_ptr mapper = create_resource_grid_mapper(max_ports, NRE * max_prb, grid); // Process. processor->process(*mapper, test_case.config); diff --git a/tests/unittests/phy/upper/channel_processors/pdsch_modulator_test.cpp b/tests/unittests/phy/upper/channel_processors/pdsch_modulator_test.cpp index 7237308dbf..4fd4a234ae 100644 --- a/tests/unittests/phy/upper/channel_processors/pdsch_modulator_test.cpp +++ b/tests/unittests/phy/upper/channel_processors/pdsch_modulator_test.cpp @@ -51,13 +51,13 @@ int main() test_case.config.freq_allocation.get_prb_mask(test_case.config.bwp_start_rb, test_case.config.bwp_size_rb); int prb_idx_high = prb_mask.find_highest(); TESTASSERT(prb_idx_high > 1); - unsigned max_prb = static_cast(prb_idx_high + 1); - unsigned max_symb = get_nsymb_per_slot(cyclic_prefix::NORMAL); + unsigned max_prb = static_cast(prb_idx_high + 1); + unsigned max_symb = get_nsymb_per_slot(cyclic_prefix::NORMAL); + unsigned max_ports = test_case.config.precoding.get_nof_ports(); // Prepare resource grid and resource grid mapper spies. - resource_grid_writer_spy grid(MAX_PORTS, max_symb, max_prb); - std::unique_ptr mapper = - create_resource_grid_mapper(MAX_PORTS, max_symb, NRE * max_prb, grid); + resource_grid_writer_spy grid(max_ports, max_symb, max_prb); + std::unique_ptr mapper = create_resource_grid_mapper(max_ports, NRE * max_prb, grid); // Read codeword. std::vector data = test_case.data.read(); diff --git a/tests/unittests/phy/upper/channel_processors/pdsch_processor_test_doubles.h b/tests/unittests/phy/upper/channel_processors/pdsch_processor_test_doubles.h index ef2306dcfa..5be1a2af83 100644 --- a/tests/unittests/phy/upper/channel_processors/pdsch_processor_test_doubles.h +++ b/tests/unittests/phy/upper/channel_processors/pdsch_processor_test_doubles.h @@ -23,6 +23,7 @@ #pragma once #include "srsran/phy/upper/channel_processors/pdsch_processor.h" +#include namespace srsran { @@ -33,14 +34,34 @@ class pdsch_processor_spy : public pdsch_processor public: void process(resource_grid_mapper& mapper, + pdsch_processor_notifier& notifier, static_vector, MAX_NOF_TRANSPORT_BLOCKS> data, const pdu_t& pdu) override { process_method_called = true; + notifier.on_finish_processing(); } /// Returns true if the process method has been called, false otherwise. bool is_process_called() const { return process_method_called; } }; +class pdsch_processor_notifier_spy : public pdsch_processor_notifier +{ +public: + void on_finish_processing() override { finished = true; } + + void wait_for_finished() + { + while (!finished) { + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + } + + void reset() { finished = false; } + +private: + std::atomic finished = {}; +}; + } // namespace srsran diff --git a/tests/unittests/phy/upper/channel_processors/pdsch_processor_unittest.cpp b/tests/unittests/phy/upper/channel_processors/pdsch_processor_unittest.cpp index 4777d53e3e..9d4a5a79c4 100644 --- a/tests/unittests/phy/upper/channel_processors/pdsch_processor_unittest.cpp +++ b/tests/unittests/phy/upper/channel_processors/pdsch_processor_unittest.cpp @@ -24,6 +24,7 @@ #include "../signal_processors/dmrs_pdsch_processor_test_doubles.h" #include "pdsch_encoder_test_doubles.h" #include "pdsch_modulator_test_doubles.h" +#include "pdsch_processor_test_doubles.h" #include "srsran/phy/upper/channel_processors/channel_processor_factories.h" #include "srsran/phy/upper/channel_processors/pdsch_processor.h" #include "srsran/ran/precoding/precoding_codebooks.h" @@ -128,9 +129,10 @@ TEST_P(PdschProcessorFixture, UnitTest) std::unique_ptr pdsch = pdsch_proc_factory->create(); ASSERT_NE(pdsch, nullptr); - pdsch_encoder_spy* encoder_spy = encoder_factory_spy->get_entries().front(); - pdsch_modulator_spy* modulator_spy = modulator_factory_spy->get_entries().front(); - dmrs_pdsch_processor_spy* dmrs_spy = dmrs_factory_spy->get_entries().front(); + pdsch_encoder_spy* encoder_spy = encoder_factory_spy->get_entries().front(); + pdsch_modulator_spy* modulator_spy = modulator_factory_spy->get_entries().front(); + dmrs_pdsch_processor_spy* dmrs_spy = dmrs_factory_spy->get_entries().front(); + pdsch_processor_notifier_spy notifier; unsigned nof_codewords = (nof_layers > 4) ? 2 : 1; unsigned nof_symbols_slot = get_nsymb_per_slot(cp); @@ -209,9 +211,13 @@ TEST_P(PdschProcessorFixture, UnitTest) encoder_spy->reset(); modulator_spy->reset(); dmrs_spy->reset(); + notifier.reset(); // Process PDU. - pdsch->process(mapper_dummy, data, pdu); + pdsch->process(mapper_dummy, notifier, data, pdu); + + // Wait for the processor to finish. + notifier.wait_for_finished(); unsigned nof_layers_cw0 = nof_layers / nof_codewords; unsigned nof_layers_cw1 = nof_layers - nof_layers_cw0; diff --git a/tests/unittests/phy/upper/channel_processors/pdsch_processor_validator_test.cpp b/tests/unittests/phy/upper/channel_processors/pdsch_processor_validator_test.cpp index fca9d81cdd..c2caf79fe5 100644 --- a/tests/unittests/phy/upper/channel_processors/pdsch_processor_validator_test.cpp +++ b/tests/unittests/phy/upper/channel_processors/pdsch_processor_validator_test.cpp @@ -21,8 +21,8 @@ */ #include "../../support/resource_grid_mapper_test_doubles.h" -#include "../../support/resource_grid_test_doubles.h" #include "../rx_softbuffer_test_doubles.h" +#include "pdsch_processor_test_doubles.h" #include "srsran/phy/support/support_factories.h" #include "srsran/phy/upper/channel_processors/channel_processor_factories.h" #include "srsran/phy/upper/channel_processors/channel_processor_formatters.h" @@ -262,7 +262,7 @@ TEST_P(pdschProcessorFixture, pdschProcessorValidatorDeathTest) // Prepare resource grid and resource grid mapper spies. resource_grid_writer_spy grid(0, 0, 0); - std::unique_ptr mapper = create_resource_grid_mapper(0, 0, 0, grid); + std::unique_ptr mapper = create_resource_grid_mapper(0, 0, grid); // Prepare receive data. std::vector data; @@ -270,9 +270,11 @@ TEST_P(pdschProcessorFixture, pdschProcessorValidatorDeathTest) // Prepare softbuffer. rx_softbuffer_spy softbuffer(ldpc::MAX_CODEBLOCK_SIZE, 0); + pdsch_processor_notifier_spy notifier_spy; + // Process pdsch PDU. #ifdef ASSERTS_ENABLED - ASSERT_DEATH({ pdsch_proc->process(*mapper, {data}, param.get_pdu()); }, param.expr); + ASSERT_DEATH({ pdsch_proc->process(*mapper, notifier_spy, {data}, param.get_pdu()); }, param.expr); #endif // ASSERTS_ENABLED } diff --git a/tests/unittests/phy/upper/channel_processors/pdsch_processor_vectortest.cpp b/tests/unittests/phy/upper/channel_processors/pdsch_processor_vectortest.cpp index dafe64235b..82c51aa4b1 100644 --- a/tests/unittests/phy/upper/channel_processors/pdsch_processor_vectortest.cpp +++ b/tests/unittests/phy/upper/channel_processors/pdsch_processor_vectortest.cpp @@ -22,6 +22,7 @@ #include "../../support/resource_grid_mapper_test_doubles.h" #include "pdsch_processor_test_data.h" +#include "pdsch_processor_test_doubles.h" #include "srsran/phy/support/support_factories.h" #include "srsran/phy/upper/channel_processors/channel_processor_factories.h" #include "srsran/phy/upper/channel_processors/channel_processor_formatters.h" @@ -51,8 +52,8 @@ class PdschProcessorFixture : public ::testing::TestWithParam create_pdsch_processor_factory(const std::string& type) { - std::shared_ptr crc_calculator_factory = create_crc_calculator_factory_sw("auto"); - if (!crc_calculator_factory) { + std::shared_ptr crc_calc_factory = create_crc_calculator_factory_sw("auto"); + if (!crc_calc_factory) { return nullptr; } @@ -67,7 +68,7 @@ class PdschProcessorFixture : public ::testing::TestWithParam ldpc_segmenter_tx_factory = - create_ldpc_segmenter_tx_factory_sw(crc_calculator_factory); + create_ldpc_segmenter_tx_factory_sw(crc_calc_factory); if (!ldpc_segmenter_tx_factory) { return nullptr; } @@ -109,13 +110,16 @@ class PdschProcessorFixture : public ::testing::TestWithParam>(NOF_CONCURRENT_THREADS, 128, "pdsch_proc"); + executor = std::make_unique>(*worker_pool); + + return create_pdsch_concurrent_processor_factory_sw(crc_calc_factory, ldpc_encoder_factory, ldpc_rate_matcher_factory, prg_factory, modulator_factory, dmrs_pdsch_factory, - executor, + *executor, NOF_CONCURRENT_THREADS); } @@ -137,8 +141,8 @@ class PdschProcessorFixture : public ::testing::TestWithParam pdu_validator; // Worker pool. - static task_worker_pool worker_pool; - static task_worker_pool_executor executor; + std::unique_ptr> worker_pool; + std::unique_ptr> executor; void SetUp() override { @@ -158,25 +162,29 @@ class PdschProcessorFixture : public ::testing::TestWithParamstop(); + } + } }; -task_worker_pool PdschProcessorFixture::worker_pool(NOF_CONCURRENT_THREADS, 128, "pdsch_proc"); -task_worker_pool_executor PdschProcessorFixture::executor(PdschProcessorFixture::worker_pool); - TEST_P(PdschProcessorFixture, PdschProcessorVectortest) { + pdsch_processor_notifier_spy notifier_spy; const PdschProcessorParams& param = GetParam(); const test_case_t& test_case = std::get<1>(param); const test_case_context& context = test_case.context; const pdsch_processor::pdu_t& config = context.pdu; - unsigned max_symb = context.rg_nof_symb; - unsigned max_prb = context.rg_nof_rb; + unsigned max_symb = context.rg_nof_symb; + unsigned max_prb = context.rg_nof_rb; + unsigned max_ports = config.precoding.get_nof_ports(); // Prepare resource grid and resource grid mapper spies. - resource_grid_writer_spy grid(MAX_PORTS, max_symb, max_prb); - std::unique_ptr mapper = create_resource_grid_mapper(MAX_PORTS, max_symb, NRE * max_prb, grid); + resource_grid_writer_spy grid(max_ports, max_symb, max_prb); + std::unique_ptr mapper = create_resource_grid_mapper(max_ports, NRE * max_prb, grid); // Read input data as a bit-packed transport block. std::vector transport_block = test_case.sch_data.read(); @@ -190,7 +198,10 @@ TEST_P(PdschProcessorFixture, PdschProcessorVectortest) ASSERT_TRUE(pdu_validator->is_valid(config)); // Process PDSCH. - pdsch_proc->process(*mapper, transport_blocks, config); + pdsch_proc->process(*mapper, notifier_spy, transport_blocks, config); + + // Waits for the processor to finish. + notifier_spy.wait_for_finished(); // Assert results. grid.assert_entries(test_case.grid_expected.read()); @@ -203,4 +214,4 @@ INSTANTIATE_TEST_SUITE_P(PdschProcessorVectortest, ::testing::ValuesIn(pdsch_processor_test_data))); } // namespace -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_test_data.h b/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_test_data.h index 9fea03f925..ac25b94d63 100644 --- a/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_test_data.h +++ b/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_test_data.h @@ -22,7 +22,7 @@ #pragma once -// This file was generated using the following MATLAB class on 14-09-2023 (seed 0): +// This file was generated using the following MATLAB class on 02-10-2023 (seed 0): // + "srsPUCCHProcessorFormat1Unittest.m" #include "../../support/resource_grid_test_doubles.h" @@ -39,54 +39,54 @@ struct test_case_t { static const std::vector pucch_processor_format1_test_data = { // clang-format off - {{nullopt, {0, 1309}, 51, 1, cyclic_prefix::NORMAL, 32, {}, 821, 0, {0}, 10, 14, 0, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols0.dat"}}, - {{nullopt, {0, 7482}, 51, 1, cyclic_prefix::NORMAL, 49, {}, 304, 1, {0}, 7, 14, 0, 2}, {0}, {"test_data/pucch_processor_format1_test_input_symbols1.dat"}}, - {{nullopt, {0, 8339}, 51, 1, cyclic_prefix::NORMAL, 29, {}, 155, 2, {0}, 2, 14, 0, 0}, {1, 0}, {"test_data/pucch_processor_format1_test_input_symbols2.dat"}}, - {{nullopt, {0, 8504}, 51, 1, cyclic_prefix::NORMAL, 3, {}, 268, 0, {0}, 5, 13, 1, 4}, {}, {"test_data/pucch_processor_format1_test_input_symbols3.dat"}}, - {{nullopt, {0, 10131}, 51, 1, cyclic_prefix::NORMAL, 44, {}, 719, 1, {0}, 7, 13, 1, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols4.dat"}}, - {{nullopt, {0, 9255}, 51, 1, cyclic_prefix::NORMAL, 3, {}, 229, 2, {0}, 6, 13, 1, 1}, {1, 1}, {"test_data/pucch_processor_format1_test_input_symbols5.dat"}}, - {{nullopt, {0, 2620}, 51, 1, cyclic_prefix::NORMAL, 44, {}, 789, 0, {0}, 5, 5, 5, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols6.dat"}}, - {{nullopt, {0, 3455}, 51, 1, cyclic_prefix::NORMAL, 17, {}, 479, 1, {0}, 1, 5, 5, 1}, {1}, {"test_data/pucch_processor_format1_test_input_symbols7.dat"}}, - {{nullopt, {0, 6081}, 51, 1, cyclic_prefix::NORMAL, 2, {}, 434, 2, {0}, 9, 5, 5, 0}, {0, 1}, {"test_data/pucch_processor_format1_test_input_symbols8.dat"}}, - {{nullopt, {0, 107}, 51, 1, cyclic_prefix::NORMAL, 42, {}, 312, 0, {0}, 11, 4, 10, 1}, {}, {"test_data/pucch_processor_format1_test_input_symbols9.dat"}}, - {{nullopt, {0, 102}, 51, 1, cyclic_prefix::NORMAL, 32, {}, 187, 1, {0}, 10, 4, 10, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols10.dat"}}, - {{nullopt, {0, 10156}, 51, 1, cyclic_prefix::NORMAL, 16, {}, 174, 2, {0}, 9, 4, 10, 1}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols11.dat"}}, - {{nullopt, {0, 5369}, 51, 1, cyclic_prefix::NORMAL, 10, {33}, 39, 0, {0}, 8, 14, 0, 2}, {}, {"test_data/pucch_processor_format1_test_input_symbols12.dat"}}, - {{nullopt, {0, 927}, 51, 1, cyclic_prefix::NORMAL, 28, {50}, 635, 1, {0}, 9, 14, 0, 1}, {1}, {"test_data/pucch_processor_format1_test_input_symbols13.dat"}}, - {{nullopt, {0, 2275}, 51, 1, cyclic_prefix::NORMAL, 17, {23}, 557, 2, {0}, 1, 14, 0, 0}, {0, 1}, {"test_data/pucch_processor_format1_test_input_symbols14.dat"}}, - {{nullopt, {0, 3707}, 51, 1, cyclic_prefix::NORMAL, 29, {3}, 849, 0, {0}, 7, 13, 1, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols15.dat"}}, - {{nullopt, {0, 7099}, 51, 1, cyclic_prefix::NORMAL, 37, {18}, 853, 1, {0}, 2, 13, 1, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols16.dat"}}, - {{nullopt, {0, 9430}, 51, 1, cyclic_prefix::NORMAL, 25, {41}, 621, 2, {0}, 10, 13, 1, 1}, {1, 0}, {"test_data/pucch_processor_format1_test_input_symbols17.dat"}}, - {{nullopt, {0, 4640}, 51, 1, cyclic_prefix::NORMAL, 32, {3}, 386, 0, {0}, 7, 5, 5, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols18.dat"}}, - {{nullopt, {0, 6436}, 51, 1, cyclic_prefix::NORMAL, 50, {8}, 458, 1, {0}, 4, 5, 5, 0}, {0}, {"test_data/pucch_processor_format1_test_input_symbols19.dat"}}, - {{nullopt, {0, 750}, 51, 1, cyclic_prefix::NORMAL, 47, {1}, 350, 2, {0}, 11, 5, 5, 0}, {1, 1}, {"test_data/pucch_processor_format1_test_input_symbols20.dat"}}, - {{nullopt, {0, 3929}, 51, 1, cyclic_prefix::NORMAL, 44, {12}, 547, 0, {0}, 2, 4, 10, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols21.dat"}}, - {{nullopt, {0, 6011}, 51, 1, cyclic_prefix::NORMAL, 30, {34}, 798, 1, {0}, 8, 4, 10, 0}, {0}, {"test_data/pucch_processor_format1_test_input_symbols22.dat"}}, - {{nullopt, {0, 5182}, 51, 1, cyclic_prefix::NORMAL, 6, {32}, 190, 2, {0}, 1, 4, 10, 0}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols23.dat"}}, - {{nullopt, {1, 8413}, 51, 1, cyclic_prefix::NORMAL, 6, {}, 16, 0, {0}, 10, 14, 0, 3}, {}, {"test_data/pucch_processor_format1_test_input_symbols24.dat"}}, - {{nullopt, {1, 9695}, 51, 1, cyclic_prefix::NORMAL, 45, {}, 593, 1, {0}, 11, 14, 0, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols25.dat"}}, - {{nullopt, {1, 12958}, 51, 1, cyclic_prefix::NORMAL, 14, {}, 134, 2, {0}, 0, 14, 0, 0}, {1, 1}, {"test_data/pucch_processor_format1_test_input_symbols26.dat"}}, - {{nullopt, {1, 16472}, 51, 1, cyclic_prefix::NORMAL, 41, {}, 419, 0, {0}, 0, 13, 1, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols27.dat"}}, - {{nullopt, {1, 20460}, 51, 1, cyclic_prefix::NORMAL, 7, {}, 84, 1, {0}, 3, 13, 1, 1}, {1}, {"test_data/pucch_processor_format1_test_input_symbols28.dat"}}, - {{nullopt, {1, 9875}, 51, 1, cyclic_prefix::NORMAL, 22, {}, 617, 2, {0}, 8, 13, 1, 1}, {1, 1}, {"test_data/pucch_processor_format1_test_input_symbols29.dat"}}, - {{nullopt, {1, 20311}, 51, 1, cyclic_prefix::NORMAL, 30, {}, 349, 0, {0}, 2, 5, 5, 1}, {}, {"test_data/pucch_processor_format1_test_input_symbols30.dat"}}, - {{nullopt, {1, 2079}, 51, 1, cyclic_prefix::NORMAL, 13, {}, 634, 1, {0}, 11, 5, 5, 0}, {0}, {"test_data/pucch_processor_format1_test_input_symbols31.dat"}}, - {{nullopt, {1, 11471}, 51, 1, cyclic_prefix::NORMAL, 42, {}, 779, 2, {0}, 2, 5, 5, 1}, {0, 1}, {"test_data/pucch_processor_format1_test_input_symbols32.dat"}}, - {{nullopt, {1, 6540}, 51, 1, cyclic_prefix::NORMAL, 43, {}, 414, 0, {0}, 6, 4, 10, 1}, {}, {"test_data/pucch_processor_format1_test_input_symbols33.dat"}}, - {{nullopt, {1, 16068}, 51, 1, cyclic_prefix::NORMAL, 0, {}, 620, 1, {0}, 8, 4, 10, 0}, {0}, {"test_data/pucch_processor_format1_test_input_symbols34.dat"}}, - {{nullopt, {1, 20150}, 51, 1, cyclic_prefix::NORMAL, 16, {}, 981, 2, {0}, 11, 4, 10, 1}, {0, 1}, {"test_data/pucch_processor_format1_test_input_symbols35.dat"}}, - {{nullopt, {1, 7643}, 51, 1, cyclic_prefix::NORMAL, 2, {36}, 984, 0, {0}, 2, 14, 0, 1}, {}, {"test_data/pucch_processor_format1_test_input_symbols36.dat"}}, - {{nullopt, {1, 9421}, 51, 1, cyclic_prefix::NORMAL, 48, {10}, 318, 1, {0}, 8, 14, 0, 0}, {0}, {"test_data/pucch_processor_format1_test_input_symbols37.dat"}}, - {{nullopt, {1, 4590}, 51, 1, cyclic_prefix::NORMAL, 46, {5}, 189, 2, {0}, 0, 14, 0, 1}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols38.dat"}}, - {{nullopt, {1, 13296}, 51, 1, cyclic_prefix::NORMAL, 19, {15}, 987, 0, {0}, 5, 13, 1, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols39.dat"}}, - {{nullopt, {1, 15999}, 51, 1, cyclic_prefix::NORMAL, 2, {1}, 16, 1, {0}, 2, 13, 1, 2}, {0}, {"test_data/pucch_processor_format1_test_input_symbols40.dat"}}, - {{nullopt, {1, 12636}, 51, 1, cyclic_prefix::NORMAL, 18, {29}, 756, 2, {0}, 3, 13, 1, 2}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols41.dat"}}, - {{nullopt, {1, 8971}, 51, 1, cyclic_prefix::NORMAL, 17, {39}, 308, 0, {0}, 6, 5, 5, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols42.dat"}}, - {{nullopt, {1, 11059}, 51, 1, cyclic_prefix::NORMAL, 38, {3}, 987, 1, {0}, 7, 5, 5, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols43.dat"}}, - {{nullopt, {1, 6974}, 51, 1, cyclic_prefix::NORMAL, 40, {28}, 538, 2, {0}, 8, 5, 5, 0}, {1, 1}, {"test_data/pucch_processor_format1_test_input_symbols44.dat"}}, - {{nullopt, {1, 17581}, 51, 1, cyclic_prefix::NORMAL, 12, {36}, 213, 0, {0}, 10, 4, 10, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols45.dat"}}, - {{nullopt, {1, 5353}, 51, 1, cyclic_prefix::NORMAL, 20, {44}, 716, 1, {0}, 8, 4, 10, 0}, {0}, {"test_data/pucch_processor_format1_test_input_symbols46.dat"}}, - {{nullopt, {1, 5644}, 51, 1, cyclic_prefix::NORMAL, 18, {42}, 112, 2, {0}, 11, 4, 10, 0}, {0, 1}, {"test_data/pucch_processor_format1_test_input_symbols47.dat"}}, + {{nullopt, {0, 1309}, 51, 1, cyclic_prefix::NORMAL, 32, {}, 821, 0, {0,1,2,3,}, 10, 14, 0, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols0.dat"}}, + {{nullopt, {0, 1961}, 51, 1, cyclic_prefix::NORMAL, 29, {}, 646, 1, {0,1,2,3,}, 6, 14, 0, 4}, {0}, {"test_data/pucch_processor_format1_test_input_symbols1.dat"}}, + {{nullopt, {0, 7404}, 51, 1, cyclic_prefix::NORMAL, 49, {}, 803, 2, {0,1,2,3,}, 11, 14, 0, 0}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols2.dat"}}, + {{nullopt, {0, 7367}, 51, 1, cyclic_prefix::NORMAL, 45, {}, 270, 0, {0,1,2,3,}, 8, 13, 1, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols3.dat"}}, + {{nullopt, {0, 4829}, 51, 1, cyclic_prefix::NORMAL, 25, {}, 530, 1, {0,1,2,3,}, 7, 13, 1, 4}, {0}, {"test_data/pucch_processor_format1_test_input_symbols4.dat"}}, + {{nullopt, {0, 4997}, 51, 1, cyclic_prefix::NORMAL, 13, {}, 436, 2, {0,1,2,3,}, 10, 13, 1, 1}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols5.dat"}}, + {{nullopt, {0, 1932}, 51, 1, cyclic_prefix::NORMAL, 36, {}, 44, 0, {0,1,2,3,}, 11, 5, 5, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols6.dat"}}, + {{nullopt, {0, 3489}, 51, 1, cyclic_prefix::NORMAL, 39, {}, 685, 1, {0,1,2,3,}, 3, 5, 5, 1}, {0}, {"test_data/pucch_processor_format1_test_input_symbols7.dat"}}, + {{nullopt, {0, 4353}, 51, 1, cyclic_prefix::NORMAL, 2, {}, 570, 2, {0,1,2,3,}, 7, 5, 5, 0}, {1, 0}, {"test_data/pucch_processor_format1_test_input_symbols8.dat"}}, + {{nullopt, {0, 6512}, 51, 1, cyclic_prefix::NORMAL, 43, {}, 223, 0, {0,1,2,3,}, 2, 4, 10, 1}, {}, {"test_data/pucch_processor_format1_test_input_symbols9.dat"}}, + {{nullopt, {0, 1970}, 51, 1, cyclic_prefix::NORMAL, 12, {}, 150, 1, {0,1,2,3,}, 3, 4, 10, 0}, {0}, {"test_data/pucch_processor_format1_test_input_symbols10.dat"}}, + {{nullopt, {0, 2814}, 51, 1, cyclic_prefix::NORMAL, 8, {}, 640, 2, {0,1,2,3,}, 7, 4, 10, 1}, {1, 0}, {"test_data/pucch_processor_format1_test_input_symbols11.dat"}}, + {{nullopt, {0, 525}, 51, 1, cyclic_prefix::NORMAL, 47, {19}, 605, 0, {0,1,2,3,}, 7, 14, 0, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols12.dat"}}, + {{nullopt, {0, 6431}, 51, 1, cyclic_prefix::NORMAL, 32, {12}, 260, 1, {0,1,2,3,}, 2, 14, 0, 2}, {0}, {"test_data/pucch_processor_format1_test_input_symbols13.dat"}}, + {{nullopt, {0, 522}, 51, 1, cyclic_prefix::NORMAL, 16, {0}, 877, 2, {0,1,2,3,}, 3, 14, 0, 2}, {1, 1}, {"test_data/pucch_processor_format1_test_input_symbols14.dat"}}, + {{nullopt, {0, 1381}, 51, 1, cyclic_prefix::NORMAL, 12, {26}, 251, 0, {0,1,2,3,}, 4, 13, 1, 2}, {}, {"test_data/pucch_processor_format1_test_input_symbols15.dat"}}, + {{nullopt, {0, 10059}, 51, 1, cyclic_prefix::NORMAL, 2, {22}, 976, 1, {0,1,2,3,}, 8, 13, 1, 1}, {0}, {"test_data/pucch_processor_format1_test_input_symbols16.dat"}}, + {{nullopt, {0, 3028}, 51, 1, cyclic_prefix::NORMAL, 12, {4}, 411, 2, {0,1,2,3,}, 3, 13, 1, 0}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols17.dat"}}, + {{nullopt, {0, 2832}, 51, 1, cyclic_prefix::NORMAL, 17, {10}, 598, 0, {0,1,2,3,}, 1, 5, 5, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols18.dat"}}, + {{nullopt, {0, 4025}, 51, 1, cyclic_prefix::NORMAL, 43, {7}, 575, 1, {0,1,2,3,}, 5, 5, 5, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols19.dat"}}, + {{nullopt, {0, 8202}, 51, 1, cyclic_prefix::NORMAL, 13, {31}, 94, 2, {0,1,2,3,}, 7, 5, 5, 0}, {1, 1}, {"test_data/pucch_processor_format1_test_input_symbols20.dat"}}, + {{nullopt, {0, 4061}, 51, 1, cyclic_prefix::NORMAL, 10, {12}, 510, 0, {0,1,2,3,}, 9, 4, 10, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols21.dat"}}, + {{nullopt, {0, 138}, 51, 1, cyclic_prefix::NORMAL, 9, {22}, 80, 1, {0,1,2,3,}, 4, 4, 10, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols22.dat"}}, + {{nullopt, {0, 9809}, 51, 1, cyclic_prefix::NORMAL, 41, {46}, 516, 2, {0,1,2,3,}, 4, 4, 10, 0}, {1, 0}, {"test_data/pucch_processor_format1_test_input_symbols23.dat"}}, + {{nullopt, {1, 15065}, 51, 1, cyclic_prefix::NORMAL, 20, {}, 512, 0, {0,1,2,3,}, 9, 14, 0, 1}, {}, {"test_data/pucch_processor_format1_test_input_symbols24.dat"}}, + {{nullopt, {1, 3746}, 51, 1, cyclic_prefix::NORMAL, 4, {}, 81, 1, {0,1,2,3,}, 0, 14, 0, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols25.dat"}}, + {{nullopt, {1, 4988}, 51, 1, cyclic_prefix::NORMAL, 32, {}, 147, 2, {0,1,2,3,}, 9, 14, 0, 6}, {1, 1}, {"test_data/pucch_processor_format1_test_input_symbols26.dat"}}, + {{nullopt, {1, 5453}, 51, 1, cyclic_prefix::NORMAL, 43, {}, 99, 0, {0,1,2,3,}, 9, 13, 1, 5}, {}, {"test_data/pucch_processor_format1_test_input_symbols27.dat"}}, + {{nullopt, {1, 18883}, 51, 1, cyclic_prefix::NORMAL, 4, {}, 853, 1, {0,1,2,3,}, 4, 13, 1, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols28.dat"}}, + {{nullopt, {1, 3571}, 51, 1, cyclic_prefix::NORMAL, 17, {}, 748, 2, {0,1,2,3,}, 6, 13, 1, 1}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols29.dat"}}, + {{nullopt, {1, 11264}, 51, 1, cyclic_prefix::NORMAL, 37, {}, 85, 0, {0,1,2,3,}, 1, 5, 5, 1}, {}, {"test_data/pucch_processor_format1_test_input_symbols30.dat"}}, + {{nullopt, {1, 6768}, 51, 1, cyclic_prefix::NORMAL, 22, {}, 380, 1, {0,1,2,3,}, 8, 5, 5, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols31.dat"}}, + {{nullopt, {1, 20064}, 51, 1, cyclic_prefix::NORMAL, 36, {}, 353, 2, {0,1,2,3,}, 11, 5, 5, 1}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols32.dat"}}, + {{nullopt, {1, 19546}, 51, 1, cyclic_prefix::NORMAL, 41, {}, 337, 0, {0,1,2,3,}, 9, 4, 10, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols33.dat"}}, + {{nullopt, {1, 8563}, 51, 1, cyclic_prefix::NORMAL, 32, {}, 506, 1, {0,1,2,3,}, 2, 4, 10, 0}, {0}, {"test_data/pucch_processor_format1_test_input_symbols34.dat"}}, + {{nullopt, {1, 3719}, 51, 1, cyclic_prefix::NORMAL, 44, {}, 1004, 2, {0,1,2,3,}, 1, 4, 10, 0}, {0, 1}, {"test_data/pucch_processor_format1_test_input_symbols35.dat"}}, + {{nullopt, {1, 4469}, 51, 1, cyclic_prefix::NORMAL, 50, {14}, 56, 0, {0,1,2,3,}, 11, 14, 0, 1}, {}, {"test_data/pucch_processor_format1_test_input_symbols36.dat"}}, + {{nullopt, {1, 759}, 51, 1, cyclic_prefix::NORMAL, 39, {46}, 55, 1, {0,1,2,3,}, 9, 14, 0, 1}, {0}, {"test_data/pucch_processor_format1_test_input_symbols37.dat"}}, + {{nullopt, {1, 17925}, 51, 1, cyclic_prefix::NORMAL, 47, {28}, 482, 2, {0,1,2,3,}, 7, 14, 0, 1}, {1, 0}, {"test_data/pucch_processor_format1_test_input_symbols38.dat"}}, + {{nullopt, {1, 5020}, 51, 1, cyclic_prefix::NORMAL, 27, {3}, 107, 0, {0,1,2,3,}, 1, 13, 1, 1}, {}, {"test_data/pucch_processor_format1_test_input_symbols39.dat"}}, + {{nullopt, {1, 14020}, 51, 1, cyclic_prefix::NORMAL, 8, {31}, 278, 1, {0,1,2,3,}, 2, 13, 1, 2}, {0}, {"test_data/pucch_processor_format1_test_input_symbols40.dat"}}, + {{nullopt, {1, 9687}, 51, 1, cyclic_prefix::NORMAL, 19, {26}, 927, 2, {0,1,2,3,}, 9, 13, 1, 0}, {0, 1}, {"test_data/pucch_processor_format1_test_input_symbols41.dat"}}, + {{nullopt, {1, 7167}, 51, 1, cyclic_prefix::NORMAL, 13, {50}, 311, 0, {0,1,2,3,}, 3, 5, 5, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols42.dat"}}, + {{nullopt, {1, 15566}, 51, 1, cyclic_prefix::NORMAL, 43, {31}, 0, 1, {0,1,2,3,}, 7, 5, 5, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols43.dat"}}, + {{nullopt, {1, 8442}, 51, 1, cyclic_prefix::NORMAL, 17, {18}, 483, 2, {0,1,2,3,}, 4, 5, 5, 0}, {0, 0}, {"test_data/pucch_processor_format1_test_input_symbols44.dat"}}, + {{nullopt, {1, 8077}, 51, 1, cyclic_prefix::NORMAL, 9, {34}, 814, 0, {0,1,2,3,}, 9, 4, 10, 0}, {}, {"test_data/pucch_processor_format1_test_input_symbols45.dat"}}, + {{nullopt, {1, 6534}, 51, 1, cyclic_prefix::NORMAL, 45, {39}, 131, 1, {0,1,2,3,}, 6, 4, 10, 0}, {1}, {"test_data/pucch_processor_format1_test_input_symbols46.dat"}}, + {{nullopt, {1, 19409}, 51, 1, cyclic_prefix::NORMAL, 7, {0}, 225, 2, {0,1,2,3,}, 2, 4, 10, 0}, {1, 0}, {"test_data/pucch_processor_format1_test_input_symbols47.dat"}}, // clang-format on }; diff --git a/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_vectortest.cpp b/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_vectortest.cpp index ab484b5426..1622fded72 100644 --- a/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_vectortest.cpp +++ b/tests/unittests/phy/upper/channel_processors/pucch_processor_format1_vectortest.cpp @@ -142,7 +142,7 @@ class PucchProcessorFormat1Fixture : public ::testing::TestWithParam pucch_processor_format2_test_data = { // clang-format off - {{270, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 121, 149, 114, {}, 3, 0, 1, 59859, 927, 8322, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols0.dat"}, {"test_data/pucch_processor_format2_test_harq0.dat"}, {"test_data/pucch_processor_format2_test_sr0.dat"}, {"test_data/pucch_processor_format2_test_csi10.dat"}, {"test_data/pucch_processor_format2_test_csi20.dat"}}, - {{245, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 153, 92, 142, {}, 2, 0, 1, 60931, 812, 4808, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols1.dat"}, {"test_data/pucch_processor_format2_test_harq1.dat"}, {"test_data/pucch_processor_format2_test_sr1.dat"}, {"test_data/pucch_processor_format2_test_csi11.dat"}, {"test_data/pucch_processor_format2_test_csi21.dat"}}, - {{213, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 88, 125, 32, {}, 1, 0, 1, 30744, 495, 16628, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols2.dat"}, {"test_data/pucch_processor_format2_test_harq2.dat"}, {"test_data/pucch_processor_format2_test_sr2.dat"}, {"test_data/pucch_processor_format2_test_csi12.dat"}, {"test_data/pucch_processor_format2_test_csi22.dat"}}, - {{216, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 22, 194, 19, {}, 1, 0, 1, 2135, 934, 28375, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols3.dat"}, {"test_data/pucch_processor_format2_test_harq3.dat"}, {"test_data/pucch_processor_format2_test_sr3.dat"}, {"test_data/pucch_processor_format2_test_csi13.dat"}, {"test_data/pucch_processor_format2_test_csi23.dat"}}, - {{169, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 95, 74, 47, {}, 1, 0, 1, 54393, 753, 49673, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols4.dat"}, {"test_data/pucch_processor_format2_test_harq4.dat"}, {"test_data/pucch_processor_format2_test_sr4.dat"}, {"test_data/pucch_processor_format2_test_csi14.dat"}, {"test_data/pucch_processor_format2_test_csi24.dat"}}, - {{35, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 17, 18, 14, {}, 1, 0, 1, 4902, 290, 56281, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols5.dat"}, {"test_data/pucch_processor_format2_test_harq5.dat"}, {"test_data/pucch_processor_format2_test_sr5.dat"}, {"test_data/pucch_processor_format2_test_csi15.dat"}, {"test_data/pucch_processor_format2_test_csi25.dat"}}, - {{260, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 20, 240, 10, {}, 6, 0, 1, 45791, 644, 10749, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols6.dat"}, {"test_data/pucch_processor_format2_test_harq6.dat"}, {"test_data/pucch_processor_format2_test_sr6.dat"}, {"test_data/pucch_processor_format2_test_csi16.dat"}, {"test_data/pucch_processor_format2_test_csi26.dat"}}, - {{247, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 171, 76, 153, {}, 3, 0, 1, 5159, 43, 32563, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols7.dat"}, {"test_data/pucch_processor_format2_test_harq7.dat"}, {"test_data/pucch_processor_format2_test_sr7.dat"}, {"test_data/pucch_processor_format2_test_csi17.dat"}, {"test_data/pucch_processor_format2_test_csi27.dat"}}, - {{256, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 10, 246, 7, {}, 2, 0, 1, 50318, 262, 20392, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols8.dat"}, {"test_data/pucch_processor_format2_test_harq8.dat"}, {"test_data/pucch_processor_format2_test_sr8.dat"}, {"test_data/pucch_processor_format2_test_csi18.dat"}, {"test_data/pucch_processor_format2_test_csi28.dat"}}, - {{200, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 21, 179, 12, {}, 2, 0, 1, 29170, 500, 60104, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols9.dat"}, {"test_data/pucch_processor_format2_test_harq9.dat"}, {"test_data/pucch_processor_format2_test_sr9.dat"}, {"test_data/pucch_processor_format2_test_csi19.dat"}, {"test_data/pucch_processor_format2_test_csi29.dat"}}, - {{197, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 72, 125, 11, {}, 1, 0, 1, 62284, 319, 1168, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols10.dat"}, {"test_data/pucch_processor_format2_test_harq10.dat"}, {"test_data/pucch_processor_format2_test_sr10.dat"}, {"test_data/pucch_processor_format2_test_csi110.dat"}, {"test_data/pucch_processor_format2_test_csi210.dat"}}, - {{86, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 49, 37, 30, {}, 1, 0, 1, 59196, 958, 15586, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols11.dat"}, {"test_data/pucch_processor_format2_test_harq11.dat"}, {"test_data/pucch_processor_format2_test_sr11.dat"}, {"test_data/pucch_processor_format2_test_csi111.dat"}, {"test_data/pucch_processor_format2_test_csi211.dat"}}, - {{256, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 124, 132, 112, {}, 8, 0, 1, 45718, 190, 26614, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols12.dat"}, {"test_data/pucch_processor_format2_test_harq12.dat"}, {"test_data/pucch_processor_format2_test_sr12.dat"}, {"test_data/pucch_processor_format2_test_csi112.dat"}, {"test_data/pucch_processor_format2_test_csi212.dat"}}, - {{167, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 68, 99, 27, {}, 4, 0, 1, 3013, 117, 38315, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols13.dat"}, {"test_data/pucch_processor_format2_test_harq13.dat"}, {"test_data/pucch_processor_format2_test_sr13.dat"}, {"test_data/pucch_processor_format2_test_csi113.dat"}, {"test_data/pucch_processor_format2_test_csi213.dat"}}, - {{269, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 225, 44, 194, {}, 3, 0, 1, 56117, 543, 3438, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols14.dat"}, {"test_data/pucch_processor_format2_test_harq14.dat"}, {"test_data/pucch_processor_format2_test_sr14.dat"}, {"test_data/pucch_processor_format2_test_csi114.dat"}, {"test_data/pucch_processor_format2_test_csi214.dat"}}, - {{94, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 10, 84, 7, {}, 2, 0, 1, 51484, 664, 37798, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols15.dat"}, {"test_data/pucch_processor_format2_test_harq15.dat"}, {"test_data/pucch_processor_format2_test_sr15.dat"}, {"test_data/pucch_processor_format2_test_csi115.dat"}, {"test_data/pucch_processor_format2_test_csi215.dat"}}, - {{149, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 24, 125, 21, {}, 2, 0, 1, 37944, 171, 5919, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols16.dat"}, {"test_data/pucch_processor_format2_test_harq16.dat"}, {"test_data/pucch_processor_format2_test_sr16.dat"}, {"test_data/pucch_processor_format2_test_csi116.dat"}, {"test_data/pucch_processor_format2_test_csi216.dat"}}, - {{178, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 49, 129, 30, {}, 1, 0, 1, 45299, 646, 10288, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols17.dat"}, {"test_data/pucch_processor_format2_test_harq17.dat"}, {"test_data/pucch_processor_format2_test_sr17.dat"}, {"test_data/pucch_processor_format2_test_csi117.dat"}, {"test_data/pucch_processor_format2_test_csi217.dat"}}, - {{274, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 182, 92, 34, {}, 4, 0, 1, 33481, 357, 21843, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols18.dat"}, {"test_data/pucch_processor_format2_test_harq18.dat"}, {"test_data/pucch_processor_format2_test_sr18.dat"}, {"test_data/pucch_processor_format2_test_csi118.dat"}, {"test_data/pucch_processor_format2_test_csi218.dat"}}, - {{248, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 51, 197, 14, {}, 2, 0, 1, 2249, 426, 11215, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols19.dat"}, {"test_data/pucch_processor_format2_test_harq19.dat"}, {"test_data/pucch_processor_format2_test_sr19.dat"}, {"test_data/pucch_processor_format2_test_csi119.dat"}, {"test_data/pucch_processor_format2_test_csi219.dat"}}, - {{265, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 98, 167, 92, {}, 1, 0, 1, 26481, 836, 54067, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols20.dat"}, {"test_data/pucch_processor_format2_test_harq20.dat"}, {"test_data/pucch_processor_format2_test_sr20.dat"}, {"test_data/pucch_processor_format2_test_csi120.dat"}, {"test_data/pucch_processor_format2_test_csi220.dat"}}, - {{227, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 17, 210, 3, {}, 1, 0, 1, 61055, 286, 52289, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols21.dat"}, {"test_data/pucch_processor_format2_test_harq21.dat"}, {"test_data/pucch_processor_format2_test_sr21.dat"}, {"test_data/pucch_processor_format2_test_csi121.dat"}, {"test_data/pucch_processor_format2_test_csi221.dat"}}, - {{228, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 109, 119, 42, {}, 1, 0, 1, 17930, 674, 35585, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols22.dat"}, {"test_data/pucch_processor_format2_test_harq22.dat"}, {"test_data/pucch_processor_format2_test_sr22.dat"}, {"test_data/pucch_processor_format2_test_csi122.dat"}, {"test_data/pucch_processor_format2_test_csi222.dat"}}, - {{163, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 103, 60, 21, {}, 1, 0, 1, 57829, 941, 65276, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols23.dat"}, {"test_data/pucch_processor_format2_test_harq23.dat"}, {"test_data/pucch_processor_format2_test_sr23.dat"}, {"test_data/pucch_processor_format2_test_csi123.dat"}, {"test_data/pucch_processor_format2_test_csi223.dat"}}, - {{268, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 40, 228, 33, {}, 7, 0, 1, 49216, 187, 24949, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols24.dat"}, {"test_data/pucch_processor_format2_test_harq24.dat"}, {"test_data/pucch_processor_format2_test_sr24.dat"}, {"test_data/pucch_processor_format2_test_csi124.dat"}, {"test_data/pucch_processor_format2_test_csi224.dat"}}, - {{239, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 94, 145, 7, {}, 4, 0, 1, 7412, 741, 43320, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols25.dat"}, {"test_data/pucch_processor_format2_test_harq25.dat"}, {"test_data/pucch_processor_format2_test_sr25.dat"}, {"test_data/pucch_processor_format2_test_csi125.dat"}, {"test_data/pucch_processor_format2_test_csi225.dat"}}, - {{275, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 5, 270, 1, {}, 2, 0, 1, 58442, 780, 62350, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols26.dat"}, {"test_data/pucch_processor_format2_test_harq26.dat"}, {"test_data/pucch_processor_format2_test_sr26.dat"}, {"test_data/pucch_processor_format2_test_csi126.dat"}, {"test_data/pucch_processor_format2_test_csi226.dat"}}, - {{175, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 16, 159, 10, {}, 2, 0, 1, 49565, 832, 27747, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols27.dat"}, {"test_data/pucch_processor_format2_test_harq27.dat"}, {"test_data/pucch_processor_format2_test_sr27.dat"}, {"test_data/pucch_processor_format2_test_csi127.dat"}, {"test_data/pucch_processor_format2_test_csi227.dat"}}, - {{264, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 12, 252, 5, {}, 2, 0, 1, 50704, 361, 48829, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols28.dat"}, {"test_data/pucch_processor_format2_test_harq28.dat"}, {"test_data/pucch_processor_format2_test_sr28.dat"}, {"test_data/pucch_processor_format2_test_csi128.dat"}, {"test_data/pucch_processor_format2_test_csi228.dat"}}, - {{222, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 27, 195, 1, {}, 1, 0, 1, 54392, 655, 23637, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols29.dat"}, {"test_data/pucch_processor_format2_test_harq29.dat"}, {"test_data/pucch_processor_format2_test_sr29.dat"}, {"test_data/pucch_processor_format2_test_csi129.dat"}, {"test_data/pucch_processor_format2_test_csi229.dat"}}, - {{275, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 45, 230, 2, {}, 8, 0, 1, 46929, 104, 18077, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols30.dat"}, {"test_data/pucch_processor_format2_test_harq30.dat"}, {"test_data/pucch_processor_format2_test_sr30.dat"}, {"test_data/pucch_processor_format2_test_csi130.dat"}, {"test_data/pucch_processor_format2_test_csi230.dat"}}, - {{142, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 110, 32, 4, {}, 5, 0, 1, 32216, 294, 60470, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols31.dat"}, {"test_data/pucch_processor_format2_test_harq31.dat"}, {"test_data/pucch_processor_format2_test_sr31.dat"}, {"test_data/pucch_processor_format2_test_csi131.dat"}, {"test_data/pucch_processor_format2_test_csi231.dat"}}, - {{165, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 96, 69, 45, {}, 3, 0, 1, 62826, 220, 17320, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols32.dat"}, {"test_data/pucch_processor_format2_test_harq32.dat"}, {"test_data/pucch_processor_format2_test_sr32.dat"}, {"test_data/pucch_processor_format2_test_csi132.dat"}, {"test_data/pucch_processor_format2_test_csi232.dat"}}, - {{273, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 2, 271, 0, {}, 2, 0, 1, 46084, 433, 23851, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols33.dat"}, {"test_data/pucch_processor_format2_test_harq33.dat"}, {"test_data/pucch_processor_format2_test_sr33.dat"}, {"test_data/pucch_processor_format2_test_csi133.dat"}, {"test_data/pucch_processor_format2_test_csi233.dat"}}, - {{263, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 7, 256, 0, {}, 2, 0, 1, 6957, 542, 30570, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols34.dat"}, {"test_data/pucch_processor_format2_test_harq34.dat"}, {"test_data/pucch_processor_format2_test_sr34.dat"}, {"test_data/pucch_processor_format2_test_csi134.dat"}, {"test_data/pucch_processor_format2_test_csi234.dat"}}, - {{201, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 30, 171, 16, {}, 2, 0, 1, 23449, 400, 3304, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols35.dat"}, {"test_data/pucch_processor_format2_test_harq35.dat"}, {"test_data/pucch_processor_format2_test_sr35.dat"}, {"test_data/pucch_processor_format2_test_csi135.dat"}, {"test_data/pucch_processor_format2_test_csi235.dat"}}, - {{203, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 17, 186, 7, {}, 6, 0, 1, 15330, 229, 62077, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols36.dat"}, {"test_data/pucch_processor_format2_test_harq36.dat"}, {"test_data/pucch_processor_format2_test_sr36.dat"}, {"test_data/pucch_processor_format2_test_csi136.dat"}, {"test_data/pucch_processor_format2_test_csi236.dat"}}, - {{256, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 114, 142, 43, {}, 3, 0, 1, 348, 3, 64708, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols37.dat"}, {"test_data/pucch_processor_format2_test_harq37.dat"}, {"test_data/pucch_processor_format2_test_sr37.dat"}, {"test_data/pucch_processor_format2_test_csi137.dat"}, {"test_data/pucch_processor_format2_test_csi237.dat"}}, - {{273, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 16, 257, 5, {}, 2, 0, 1, 261, 836, 24693, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols38.dat"}, {"test_data/pucch_processor_format2_test_harq38.dat"}, {"test_data/pucch_processor_format2_test_sr38.dat"}, {"test_data/pucch_processor_format2_test_csi138.dat"}, {"test_data/pucch_processor_format2_test_csi238.dat"}}, - {{260, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 43, 217, 17, {}, 2, 0, 1, 61057, 1019, 62918, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols39.dat"}, {"test_data/pucch_processor_format2_test_harq39.dat"}, {"test_data/pucch_processor_format2_test_sr39.dat"}, {"test_data/pucch_processor_format2_test_csi139.dat"}, {"test_data/pucch_processor_format2_test_csi239.dat"}}, - {{149, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 27, 122, 13, {}, 1, 0, 1, 51588, 683, 39307, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols40.dat"}, {"test_data/pucch_processor_format2_test_harq40.dat"}, {"test_data/pucch_processor_format2_test_sr40.dat"}, {"test_data/pucch_processor_format2_test_csi140.dat"}, {"test_data/pucch_processor_format2_test_csi240.dat"}}, - {{93, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 50, 43, 3, {}, 1, 0, 1, 44479, 199, 22814, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols41.dat"}, {"test_data/pucch_processor_format2_test_harq41.dat"}, {"test_data/pucch_processor_format2_test_sr41.dat"}, {"test_data/pucch_processor_format2_test_csi141.dat"}, {"test_data/pucch_processor_format2_test_csi241.dat"}}, - {{126, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 117, 9, 65, {}, 9, 0, 1, 23902, 947, 56920, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols42.dat"}, {"test_data/pucch_processor_format2_test_harq42.dat"}, {"test_data/pucch_processor_format2_test_sr42.dat"}, {"test_data/pucch_processor_format2_test_csi142.dat"}, {"test_data/pucch_processor_format2_test_csi242.dat"}}, - {{233, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 58, 175, 6, {}, 5, 0, 1, 5827, 137, 47695, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols43.dat"}, {"test_data/pucch_processor_format2_test_harq43.dat"}, {"test_data/pucch_processor_format2_test_sr43.dat"}, {"test_data/pucch_processor_format2_test_csi143.dat"}, {"test_data/pucch_processor_format2_test_csi243.dat"}}, - {{50, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 8, 42, 4, {}, 3, 0, 1, 58922, 997, 62269, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols44.dat"}, {"test_data/pucch_processor_format2_test_harq44.dat"}, {"test_data/pucch_processor_format2_test_sr44.dat"}, {"test_data/pucch_processor_format2_test_csi144.dat"}, {"test_data/pucch_processor_format2_test_csi244.dat"}}, - {{183, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 28, 155, 13, {}, 2, 0, 1, 19812, 204, 22178, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols45.dat"}, {"test_data/pucch_processor_format2_test_harq45.dat"}, {"test_data/pucch_processor_format2_test_sr45.dat"}, {"test_data/pucch_processor_format2_test_csi145.dat"}, {"test_data/pucch_processor_format2_test_csi245.dat"}}, - {{244, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 57, 187, 42, {}, 2, 0, 1, 64429, 80, 11868, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols46.dat"}, {"test_data/pucch_processor_format2_test_harq46.dat"}, {"test_data/pucch_processor_format2_test_sr46.dat"}, {"test_data/pucch_processor_format2_test_csi146.dat"}, {"test_data/pucch_processor_format2_test_csi246.dat"}}, - {{182, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 94, 88, 58, {}, 2, 0, 1, 54864, 971, 12638, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols47.dat"}, {"test_data/pucch_processor_format2_test_harq47.dat"}, {"test_data/pucch_processor_format2_test_sr47.dat"}, {"test_data/pucch_processor_format2_test_csi147.dat"}, {"test_data/pucch_processor_format2_test_csi247.dat"}}, - {{235, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 77, 158, 20, {}, 15, 0, 1, 18742, 344, 21853, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols48.dat"}, {"test_data/pucch_processor_format2_test_harq48.dat"}, {"test_data/pucch_processor_format2_test_sr48.dat"}, {"test_data/pucch_processor_format2_test_csi148.dat"}, {"test_data/pucch_processor_format2_test_csi248.dat"}}, - {{176, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 79, 97, 55, {}, 8, 0, 1, 19033, 542, 17381, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols49.dat"}, {"test_data/pucch_processor_format2_test_harq49.dat"}, {"test_data/pucch_processor_format2_test_sr49.dat"}, {"test_data/pucch_processor_format2_test_csi149.dat"}, {"test_data/pucch_processor_format2_test_csi249.dat"}}, - {{100, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 29, 71, 2, {}, 5, 0, 1, 23822, 967, 26023, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols50.dat"}, {"test_data/pucch_processor_format2_test_harq50.dat"}, {"test_data/pucch_processor_format2_test_sr50.dat"}, {"test_data/pucch_processor_format2_test_csi150.dat"}, {"test_data/pucch_processor_format2_test_csi250.dat"}}, - {{228, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 124, 104, 62, {}, 4, 0, 1, 1549, 195, 39831, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols51.dat"}, {"test_data/pucch_processor_format2_test_harq51.dat"}, {"test_data/pucch_processor_format2_test_sr51.dat"}, {"test_data/pucch_processor_format2_test_csi151.dat"}, {"test_data/pucch_processor_format2_test_csi251.dat"}}, - {{261, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 108, 153, 16, {}, 3, 0, 1, 7315, 19, 52993, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols52.dat"}, {"test_data/pucch_processor_format2_test_harq52.dat"}, {"test_data/pucch_processor_format2_test_sr52.dat"}, {"test_data/pucch_processor_format2_test_csi152.dat"}, {"test_data/pucch_processor_format2_test_csi252.dat"}}, - {{138, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 105, 33, 65, {}, 2, 0, 1, 42719, 586, 55719, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols53.dat"}, {"test_data/pucch_processor_format2_test_harq53.dat"}, {"test_data/pucch_processor_format2_test_sr53.dat"}, {"test_data/pucch_processor_format2_test_csi153.dat"}, {"test_data/pucch_processor_format2_test_csi253.dat"}}, - {{242, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 41, 201, 11, {}, 7, 0, 1, 39311, 300, 38602, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols54.dat"}, {"test_data/pucch_processor_format2_test_harq54.dat"}, {"test_data/pucch_processor_format2_test_sr54.dat"}, {"test_data/pucch_processor_format2_test_csi154.dat"}, {"test_data/pucch_processor_format2_test_csi254.dat"}}, - {{237, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 129, 108, 104, {}, 4, 0, 1, 60751, 546, 37642, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols55.dat"}, {"test_data/pucch_processor_format2_test_harq55.dat"}, {"test_data/pucch_processor_format2_test_sr55.dat"}, {"test_data/pucch_processor_format2_test_csi155.dat"}, {"test_data/pucch_processor_format2_test_csi255.dat"}}, - {{270, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 96, 174, 37, {}, 2, 0, 1, 19375, 872, 44130, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols56.dat"}, {"test_data/pucch_processor_format2_test_harq56.dat"}, {"test_data/pucch_processor_format2_test_sr56.dat"}, {"test_data/pucch_processor_format2_test_csi156.dat"}, {"test_data/pucch_processor_format2_test_csi256.dat"}}, - {{270, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 6, 264, 4, {}, 2, 0, 1, 55742, 243, 43194, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols57.dat"}, {"test_data/pucch_processor_format2_test_harq57.dat"}, {"test_data/pucch_processor_format2_test_sr57.dat"}, {"test_data/pucch_processor_format2_test_csi157.dat"}, {"test_data/pucch_processor_format2_test_csi257.dat"}}, - {{222, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 14, 208, 12, {}, 2, 0, 1, 29838, 184, 42828, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols58.dat"}, {"test_data/pucch_processor_format2_test_harq58.dat"}, {"test_data/pucch_processor_format2_test_sr58.dat"}, {"test_data/pucch_processor_format2_test_csi158.dat"}, {"test_data/pucch_processor_format2_test_csi258.dat"}}, - {{251, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 102, 149, 16, {}, 1, 0, 1, 63606, 71, 54226, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols59.dat"}, {"test_data/pucch_processor_format2_test_harq59.dat"}, {"test_data/pucch_processor_format2_test_sr59.dat"}, {"test_data/pucch_processor_format2_test_csi159.dat"}, {"test_data/pucch_processor_format2_test_csi259.dat"}}, - {{251, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 40, 211, 19, {}, 15, 0, 1, 47613, 344, 47906, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols60.dat"}, {"test_data/pucch_processor_format2_test_harq60.dat"}, {"test_data/pucch_processor_format2_test_sr60.dat"}, {"test_data/pucch_processor_format2_test_csi160.dat"}, {"test_data/pucch_processor_format2_test_csi260.dat"}}, - {{267, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 39, 228, 3, {}, 8, 0, 1, 65347, 519, 52637, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols61.dat"}, {"test_data/pucch_processor_format2_test_harq61.dat"}, {"test_data/pucch_processor_format2_test_sr61.dat"}, {"test_data/pucch_processor_format2_test_csi161.dat"}, {"test_data/pucch_processor_format2_test_csi261.dat"}}, - {{266, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 36, 230, 9, {}, 5, 0, 1, 26745, 146, 17549, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols62.dat"}, {"test_data/pucch_processor_format2_test_harq62.dat"}, {"test_data/pucch_processor_format2_test_sr62.dat"}, {"test_data/pucch_processor_format2_test_csi162.dat"}, {"test_data/pucch_processor_format2_test_csi262.dat"}}, - {{153, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 37, 116, 4, {}, 4, 0, 1, 63664, 800, 25222, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols63.dat"}, {"test_data/pucch_processor_format2_test_harq63.dat"}, {"test_data/pucch_processor_format2_test_sr63.dat"}, {"test_data/pucch_processor_format2_test_csi163.dat"}, {"test_data/pucch_processor_format2_test_csi263.dat"}}, - {{75, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 20, 55, 0, {}, 3, 0, 1, 30873, 964, 19363, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols64.dat"}, {"test_data/pucch_processor_format2_test_harq64.dat"}, {"test_data/pucch_processor_format2_test_sr64.dat"}, {"test_data/pucch_processor_format2_test_csi164.dat"}, {"test_data/pucch_processor_format2_test_csi264.dat"}}, - {{240, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 27, 213, 23, {}, 2, 0, 1, 990, 795, 6159, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols65.dat"}, {"test_data/pucch_processor_format2_test_harq65.dat"}, {"test_data/pucch_processor_format2_test_sr65.dat"}, {"test_data/pucch_processor_format2_test_csi165.dat"}, {"test_data/pucch_processor_format2_test_csi265.dat"}}, - {{108, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 55, 53, 4, {}, 16, 0, 1, 52223, 672, 29093, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols66.dat"}, {"test_data/pucch_processor_format2_test_harq66.dat"}, {"test_data/pucch_processor_format2_test_sr66.dat"}, {"test_data/pucch_processor_format2_test_csi166.dat"}, {"test_data/pucch_processor_format2_test_csi266.dat"}}, - {{120, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 109, 11, 79, {}, 9, 0, 1, 49215, 116, 1826, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols67.dat"}, {"test_data/pucch_processor_format2_test_harq67.dat"}, {"test_data/pucch_processor_format2_test_sr67.dat"}, {"test_data/pucch_processor_format2_test_csi167.dat"}, {"test_data/pucch_processor_format2_test_csi267.dat"}}, - {{178, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 9, 169, 1, {}, 5, 0, 1, 57728, 218, 48092, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols68.dat"}, {"test_data/pucch_processor_format2_test_harq68.dat"}, {"test_data/pucch_processor_format2_test_sr68.dat"}, {"test_data/pucch_processor_format2_test_csi168.dat"}, {"test_data/pucch_processor_format2_test_csi268.dat"}}, - {{109, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 48, 61, 8, {}, 4, 0, 1, 57311, 93, 32626, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols69.dat"}, {"test_data/pucch_processor_format2_test_harq69.dat"}, {"test_data/pucch_processor_format2_test_sr69.dat"}, {"test_data/pucch_processor_format2_test_csi169.dat"}, {"test_data/pucch_processor_format2_test_csi269.dat"}}, - {{85, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 13, 72, 8, {}, 3, 0, 1, 28362, 838, 62209, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols70.dat"}, {"test_data/pucch_processor_format2_test_harq70.dat"}, {"test_data/pucch_processor_format2_test_sr70.dat"}, {"test_data/pucch_processor_format2_test_csi170.dat"}, {"test_data/pucch_processor_format2_test_csi270.dat"}}, - {{269, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 99, 170, 28, {}, 3, 0, 1, 36840, 438, 7651, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols71.dat"}, {"test_data/pucch_processor_format2_test_harq71.dat"}, {"test_data/pucch_processor_format2_test_sr71.dat"}, {"test_data/pucch_processor_format2_test_csi171.dat"}, {"test_data/pucch_processor_format2_test_csi271.dat"}}, - {{248, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 10, 238, 1, {}, 2, 12, 2, 50792, 485, 62950, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols72.dat"}, {"test_data/pucch_processor_format2_test_harq72.dat"}, {"test_data/pucch_processor_format2_test_sr72.dat"}, {"test_data/pucch_processor_format2_test_csi172.dat"}, {"test_data/pucch_processor_format2_test_csi272.dat"}}, - {{192, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 29, 163, 16, {}, 1, 12, 2, 58606, 213, 54228, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols73.dat"}, {"test_data/pucch_processor_format2_test_harq73.dat"}, {"test_data/pucch_processor_format2_test_sr73.dat"}, {"test_data/pucch_processor_format2_test_csi173.dat"}, {"test_data/pucch_processor_format2_test_csi273.dat"}}, - {{268, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 49, 219, 15, {}, 1, 12, 2, 25577, 199, 10524, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols74.dat"}, {"test_data/pucch_processor_format2_test_harq74.dat"}, {"test_data/pucch_processor_format2_test_sr74.dat"}, {"test_data/pucch_processor_format2_test_csi174.dat"}, {"test_data/pucch_processor_format2_test_csi274.dat"}}, - {{115, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 18, 97, 14, {}, 1, 12, 2, 25799, 586, 3708, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols75.dat"}, {"test_data/pucch_processor_format2_test_harq75.dat"}, {"test_data/pucch_processor_format2_test_sr75.dat"}, {"test_data/pucch_processor_format2_test_csi175.dat"}, {"test_data/pucch_processor_format2_test_csi275.dat"}}, - {{166, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 102, 64, 24, {}, 1, 12, 2, 18011, 70, 50068, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols76.dat"}, {"test_data/pucch_processor_format2_test_harq76.dat"}, {"test_data/pucch_processor_format2_test_sr76.dat"}, {"test_data/pucch_processor_format2_test_csi176.dat"}, {"test_data/pucch_processor_format2_test_csi276.dat"}}, - {{129, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 122, 7, 20, {}, 1, 12, 2, 684, 678, 24949, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols77.dat"}, {"test_data/pucch_processor_format2_test_harq77.dat"}, {"test_data/pucch_processor_format2_test_sr77.dat"}, {"test_data/pucch_processor_format2_test_csi177.dat"}, {"test_data/pucch_processor_format2_test_csi277.dat"}}, - {{233, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 62, 171, 7, {}, 3, 12, 2, 61135, 781, 37784, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols78.dat"}, {"test_data/pucch_processor_format2_test_harq78.dat"}, {"test_data/pucch_processor_format2_test_sr78.dat"}, {"test_data/pucch_processor_format2_test_csi178.dat"}, {"test_data/pucch_processor_format2_test_csi278.dat"}}, - {{242, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 110, 132, 30, {}, 2, 12, 2, 23858, 1003, 38485, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols79.dat"}, {"test_data/pucch_processor_format2_test_harq79.dat"}, {"test_data/pucch_processor_format2_test_sr79.dat"}, {"test_data/pucch_processor_format2_test_csi179.dat"}, {"test_data/pucch_processor_format2_test_csi279.dat"}}, - {{275, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 1, 274, 0, {}, 1, 12, 2, 33392, 106, 61721, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols80.dat"}, {"test_data/pucch_processor_format2_test_harq80.dat"}, {"test_data/pucch_processor_format2_test_sr80.dat"}, {"test_data/pucch_processor_format2_test_csi180.dat"}, {"test_data/pucch_processor_format2_test_csi280.dat"}}, - {{151, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 131, 20, 37, {}, 1, 12, 2, 12214, 430, 21722, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols81.dat"}, {"test_data/pucch_processor_format2_test_harq81.dat"}, {"test_data/pucch_processor_format2_test_sr81.dat"}, {"test_data/pucch_processor_format2_test_csi181.dat"}, {"test_data/pucch_processor_format2_test_csi281.dat"}}, - {{239, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 25, 214, 22, {}, 1, 12, 2, 52121, 593, 48598, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols82.dat"}, {"test_data/pucch_processor_format2_test_harq82.dat"}, {"test_data/pucch_processor_format2_test_sr82.dat"}, {"test_data/pucch_processor_format2_test_csi182.dat"}, {"test_data/pucch_processor_format2_test_csi282.dat"}}, - {{261, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 30, 231, 5, {}, 1, 12, 2, 49004, 360, 28770, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols83.dat"}, {"test_data/pucch_processor_format2_test_harq83.dat"}, {"test_data/pucch_processor_format2_test_sr83.dat"}, {"test_data/pucch_processor_format2_test_csi183.dat"}, {"test_data/pucch_processor_format2_test_csi283.dat"}}, - {{275, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 21, 254, 14, {}, 4, 12, 2, 28164, 339, 4304, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols84.dat"}, {"test_data/pucch_processor_format2_test_harq84.dat"}, {"test_data/pucch_processor_format2_test_sr84.dat"}, {"test_data/pucch_processor_format2_test_csi184.dat"}, {"test_data/pucch_processor_format2_test_csi284.dat"}}, - {{214, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 54, 160, 41, {}, 2, 12, 2, 18627, 717, 20620, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols85.dat"}, {"test_data/pucch_processor_format2_test_harq85.dat"}, {"test_data/pucch_processor_format2_test_sr85.dat"}, {"test_data/pucch_processor_format2_test_csi185.dat"}, {"test_data/pucch_processor_format2_test_csi285.dat"}}, - {{273, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 8, 265, 3, {}, 2, 12, 2, 34739, 567, 23197, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols86.dat"}, {"test_data/pucch_processor_format2_test_harq86.dat"}, {"test_data/pucch_processor_format2_test_sr86.dat"}, {"test_data/pucch_processor_format2_test_csi186.dat"}, {"test_data/pucch_processor_format2_test_csi286.dat"}}, - {{184, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 134, 50, 61, {}, 1, 12, 2, 14527, 762, 24941, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols87.dat"}, {"test_data/pucch_processor_format2_test_harq87.dat"}, {"test_data/pucch_processor_format2_test_sr87.dat"}, {"test_data/pucch_processor_format2_test_csi187.dat"}, {"test_data/pucch_processor_format2_test_csi287.dat"}}, - {{197, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 78, 119, 5, {}, 1, 12, 2, 61021, 934, 31675, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols88.dat"}, {"test_data/pucch_processor_format2_test_harq88.dat"}, {"test_data/pucch_processor_format2_test_sr88.dat"}, {"test_data/pucch_processor_format2_test_csi188.dat"}, {"test_data/pucch_processor_format2_test_csi288.dat"}}, - {{245, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 197, 48, 162, {}, 1, 12, 2, 20294, 447, 19692, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols89.dat"}, {"test_data/pucch_processor_format2_test_harq89.dat"}, {"test_data/pucch_processor_format2_test_sr89.dat"}, {"test_data/pucch_processor_format2_test_csi189.dat"}, {"test_data/pucch_processor_format2_test_csi289.dat"}}, - {{175, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 18, 157, 10, {}, 2, 12, 2, 1640, 90, 64542, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols90.dat"}, {"test_data/pucch_processor_format2_test_harq90.dat"}, {"test_data/pucch_processor_format2_test_sr90.dat"}, {"test_data/pucch_processor_format2_test_csi190.dat"}, {"test_data/pucch_processor_format2_test_csi290.dat"}}, - {{211, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 22, 189, 4, {}, 1, 12, 2, 2207, 991, 7536, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols91.dat"}, {"test_data/pucch_processor_format2_test_harq91.dat"}, {"test_data/pucch_processor_format2_test_sr91.dat"}, {"test_data/pucch_processor_format2_test_csi191.dat"}, {"test_data/pucch_processor_format2_test_csi291.dat"}}, - {{263, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 82, 181, 10, {}, 1, 12, 2, 5101, 206, 16073, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols92.dat"}, {"test_data/pucch_processor_format2_test_harq92.dat"}, {"test_data/pucch_processor_format2_test_sr92.dat"}, {"test_data/pucch_processor_format2_test_csi192.dat"}, {"test_data/pucch_processor_format2_test_csi292.dat"}}, - {{186, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 54, 132, 9, {}, 1, 12, 2, 64730, 623, 25773, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols93.dat"}, {"test_data/pucch_processor_format2_test_harq93.dat"}, {"test_data/pucch_processor_format2_test_sr93.dat"}, {"test_data/pucch_processor_format2_test_csi193.dat"}, {"test_data/pucch_processor_format2_test_csi293.dat"}}, - {{275, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 10, 265, 6, {}, 1, 12, 2, 13139, 854, 47466, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols94.dat"}, {"test_data/pucch_processor_format2_test_harq94.dat"}, {"test_data/pucch_processor_format2_test_sr94.dat"}, {"test_data/pucch_processor_format2_test_csi194.dat"}, {"test_data/pucch_processor_format2_test_csi294.dat"}}, - {{35, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 4, 31, 3, {}, 1, 12, 2, 9625, 854, 55975, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols95.dat"}, {"test_data/pucch_processor_format2_test_harq95.dat"}, {"test_data/pucch_processor_format2_test_sr95.dat"}, {"test_data/pucch_processor_format2_test_csi195.dat"}, {"test_data/pucch_processor_format2_test_csi295.dat"}}, - {{175, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 44, 131, 14, {}, 4, 12, 2, 62874, 815, 43359, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols96.dat"}, {"test_data/pucch_processor_format2_test_harq96.dat"}, {"test_data/pucch_processor_format2_test_sr96.dat"}, {"test_data/pucch_processor_format2_test_csi196.dat"}, {"test_data/pucch_processor_format2_test_csi296.dat"}}, - {{229, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 78, 151, 49, {}, 2, 12, 2, 174, 877, 29588, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols97.dat"}, {"test_data/pucch_processor_format2_test_harq97.dat"}, {"test_data/pucch_processor_format2_test_sr97.dat"}, {"test_data/pucch_processor_format2_test_csi197.dat"}, {"test_data/pucch_processor_format2_test_csi297.dat"}}, - {{219, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 63, 156, 38, {}, 1, 12, 2, 17795, 344, 30322, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols98.dat"}, {"test_data/pucch_processor_format2_test_harq98.dat"}, {"test_data/pucch_processor_format2_test_sr98.dat"}, {"test_data/pucch_processor_format2_test_csi198.dat"}, {"test_data/pucch_processor_format2_test_csi298.dat"}}, - {{245, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 207, 38, 8, {}, 1, 12, 2, 13858, 260, 27160, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols99.dat"}, {"test_data/pucch_processor_format2_test_harq99.dat"}, {"test_data/pucch_processor_format2_test_sr99.dat"}, {"test_data/pucch_processor_format2_test_csi199.dat"}, {"test_data/pucch_processor_format2_test_csi299.dat"}}, - {{246, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 4, 242, 2, {}, 1, 12, 2, 52959, 993, 25228, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols100.dat"}, {"test_data/pucch_processor_format2_test_harq100.dat"}, {"test_data/pucch_processor_format2_test_sr100.dat"}, {"test_data/pucch_processor_format2_test_csi1100.dat"}, {"test_data/pucch_processor_format2_test_csi2100.dat"}}, - {{151, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 107, 44, 35, {}, 1, 12, 2, 20171, 114, 23170, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols101.dat"}, {"test_data/pucch_processor_format2_test_harq101.dat"}, {"test_data/pucch_processor_format2_test_sr101.dat"}, {"test_data/pucch_processor_format2_test_csi1101.dat"}, {"test_data/pucch_processor_format2_test_csi2101.dat"}}, - {{212, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 105, 107, 57, {}, 4, 12, 2, 35044, 230, 32337, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols102.dat"}, {"test_data/pucch_processor_format2_test_harq102.dat"}, {"test_data/pucch_processor_format2_test_sr102.dat"}, {"test_data/pucch_processor_format2_test_csi1102.dat"}, {"test_data/pucch_processor_format2_test_csi2102.dat"}}, - {{185, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 27, 158, 4, {}, 3, 12, 2, 1356, 250, 49967, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols103.dat"}, {"test_data/pucch_processor_format2_test_harq103.dat"}, {"test_data/pucch_processor_format2_test_sr103.dat"}, {"test_data/pucch_processor_format2_test_csi1103.dat"}, {"test_data/pucch_processor_format2_test_csi2103.dat"}}, - {{245, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 6, 239, 3, {}, 2, 12, 2, 61574, 202, 22237, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols104.dat"}, {"test_data/pucch_processor_format2_test_harq104.dat"}, {"test_data/pucch_processor_format2_test_sr104.dat"}, {"test_data/pucch_processor_format2_test_csi1104.dat"}, {"test_data/pucch_processor_format2_test_csi2104.dat"}}, - {{213, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 23, 190, 6, {}, 1, 12, 2, 26170, 131, 59412, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols105.dat"}, {"test_data/pucch_processor_format2_test_harq105.dat"}, {"test_data/pucch_processor_format2_test_sr105.dat"}, {"test_data/pucch_processor_format2_test_csi1105.dat"}, {"test_data/pucch_processor_format2_test_csi2105.dat"}}, - {{261, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 58, 203, 22, {}, 1, 12, 2, 54536, 282, 7131, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols106.dat"}, {"test_data/pucch_processor_format2_test_harq106.dat"}, {"test_data/pucch_processor_format2_test_sr106.dat"}, {"test_data/pucch_processor_format2_test_csi1106.dat"}, {"test_data/pucch_processor_format2_test_csi2106.dat"}}, - {{272, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 64, 208, 35, {}, 1, 12, 2, 1349, 154, 64643, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols107.dat"}, {"test_data/pucch_processor_format2_test_harq107.dat"}, {"test_data/pucch_processor_format2_test_sr107.dat"}, {"test_data/pucch_processor_format2_test_csi1107.dat"}, {"test_data/pucch_processor_format2_test_csi2107.dat"}}, - {{140, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 94, 46, 35, {}, 3, 12, 2, 64852, 621, 21809, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols108.dat"}, {"test_data/pucch_processor_format2_test_harq108.dat"}, {"test_data/pucch_processor_format2_test_sr108.dat"}, {"test_data/pucch_processor_format2_test_csi1108.dat"}, {"test_data/pucch_processor_format2_test_csi2108.dat"}}, - {{233, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 63, 170, 42, {}, 2, 12, 2, 26114, 122, 58217, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols109.dat"}, {"test_data/pucch_processor_format2_test_harq109.dat"}, {"test_data/pucch_processor_format2_test_sr109.dat"}, {"test_data/pucch_processor_format2_test_csi1109.dat"}, {"test_data/pucch_processor_format2_test_csi2109.dat"}}, - {{118, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 101, 17, 15, {}, 1, 12, 2, 49743, 61, 35000, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols110.dat"}, {"test_data/pucch_processor_format2_test_harq110.dat"}, {"test_data/pucch_processor_format2_test_sr110.dat"}, {"test_data/pucch_processor_format2_test_csi1110.dat"}, {"test_data/pucch_processor_format2_test_csi2110.dat"}}, - {{204, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 40, 164, 27, {}, 1, 12, 2, 51271, 435, 52242, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols111.dat"}, {"test_data/pucch_processor_format2_test_harq111.dat"}, {"test_data/pucch_processor_format2_test_sr111.dat"}, {"test_data/pucch_processor_format2_test_csi1111.dat"}, {"test_data/pucch_processor_format2_test_csi2111.dat"}}, - {{260, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 68, 192, 16, {}, 1, 12, 2, 3596, 501, 64811, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols112.dat"}, {"test_data/pucch_processor_format2_test_harq112.dat"}, {"test_data/pucch_processor_format2_test_sr112.dat"}, {"test_data/pucch_processor_format2_test_csi1112.dat"}, {"test_data/pucch_processor_format2_test_csi2112.dat"}}, - {{237, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 134, 103, 36, {}, 1, 12, 2, 53420, 150, 16679, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols113.dat"}, {"test_data/pucch_processor_format2_test_harq113.dat"}, {"test_data/pucch_processor_format2_test_sr113.dat"}, {"test_data/pucch_processor_format2_test_csi1113.dat"}, {"test_data/pucch_processor_format2_test_csi2113.dat"}}, - {{61, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 27, 34, 7, {}, 5, 12, 2, 59339, 82, 5356, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols114.dat"}, {"test_data/pucch_processor_format2_test_harq114.dat"}, {"test_data/pucch_processor_format2_test_sr114.dat"}, {"test_data/pucch_processor_format2_test_csi1114.dat"}, {"test_data/pucch_processor_format2_test_csi2114.dat"}}, - {{232, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 125, 107, 54, {}, 3, 12, 2, 32362, 461, 50093, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols115.dat"}, {"test_data/pucch_processor_format2_test_harq115.dat"}, {"test_data/pucch_processor_format2_test_sr115.dat"}, {"test_data/pucch_processor_format2_test_csi1115.dat"}, {"test_data/pucch_processor_format2_test_csi2115.dat"}}, - {{137, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 54, 83, 8, {}, 2, 12, 2, 566, 53, 26782, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols116.dat"}, {"test_data/pucch_processor_format2_test_harq116.dat"}, {"test_data/pucch_processor_format2_test_sr116.dat"}, {"test_data/pucch_processor_format2_test_csi1116.dat"}, {"test_data/pucch_processor_format2_test_csi2116.dat"}}, - {{207, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 9, 198, 2, {}, 1, 12, 2, 14436, 166, 10232, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols117.dat"}, {"test_data/pucch_processor_format2_test_harq117.dat"}, {"test_data/pucch_processor_format2_test_sr117.dat"}, {"test_data/pucch_processor_format2_test_csi1117.dat"}, {"test_data/pucch_processor_format2_test_csi2117.dat"}}, - {{215, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 55, 160, 9, {}, 1, 12, 2, 64002, 726, 2089, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols118.dat"}, {"test_data/pucch_processor_format2_test_harq118.dat"}, {"test_data/pucch_processor_format2_test_sr118.dat"}, {"test_data/pucch_processor_format2_test_csi1118.dat"}, {"test_data/pucch_processor_format2_test_csi2118.dat"}}, - {{171, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 55, 116, 28, {}, 1, 12, 2, 39087, 31, 4347, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols119.dat"}, {"test_data/pucch_processor_format2_test_harq119.dat"}, {"test_data/pucch_processor_format2_test_sr119.dat"}, {"test_data/pucch_processor_format2_test_csi1119.dat"}, {"test_data/pucch_processor_format2_test_csi2119.dat"}}, - {{253, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 198, 55, 173, {}, 8, 12, 2, 22727, 584, 29588, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols120.dat"}, {"test_data/pucch_processor_format2_test_harq120.dat"}, {"test_data/pucch_processor_format2_test_sr120.dat"}, {"test_data/pucch_processor_format2_test_csi1120.dat"}, {"test_data/pucch_processor_format2_test_csi2120.dat"}}, - {{173, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 165, 8, 16, {}, 4, 12, 2, 33381, 720, 22497, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols121.dat"}, {"test_data/pucch_processor_format2_test_harq121.dat"}, {"test_data/pucch_processor_format2_test_sr121.dat"}, {"test_data/pucch_processor_format2_test_csi1121.dat"}, {"test_data/pucch_processor_format2_test_csi2121.dat"}}, - {{238, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 126, 112, 114, {}, 3, 12, 2, 16656, 991, 56020, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols122.dat"}, {"test_data/pucch_processor_format2_test_harq122.dat"}, {"test_data/pucch_processor_format2_test_sr122.dat"}, {"test_data/pucch_processor_format2_test_csi1122.dat"}, {"test_data/pucch_processor_format2_test_csi2122.dat"}}, - {{95, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 44, 51, 30, {}, 2, 12, 2, 31730, 544, 27571, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols123.dat"}, {"test_data/pucch_processor_format2_test_harq123.dat"}, {"test_data/pucch_processor_format2_test_sr123.dat"}, {"test_data/pucch_processor_format2_test_csi1123.dat"}, {"test_data/pucch_processor_format2_test_csi2123.dat"}}, - {{160, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 149, 11, 132, {}, 2, 12, 2, 30370, 690, 1147, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols124.dat"}, {"test_data/pucch_processor_format2_test_harq124.dat"}, {"test_data/pucch_processor_format2_test_sr124.dat"}, {"test_data/pucch_processor_format2_test_csi1124.dat"}, {"test_data/pucch_processor_format2_test_csi2124.dat"}}, - {{194, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 139, 55, 111, {}, 1, 12, 2, 27794, 277, 4660, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols125.dat"}, {"test_data/pucch_processor_format2_test_harq125.dat"}, {"test_data/pucch_processor_format2_test_sr125.dat"}, {"test_data/pucch_processor_format2_test_csi1125.dat"}, {"test_data/pucch_processor_format2_test_csi2125.dat"}}, - {{237, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 12, 225, 8, {}, 4, 12, 2, 30600, 726, 63364, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols126.dat"}, {"test_data/pucch_processor_format2_test_harq126.dat"}, {"test_data/pucch_processor_format2_test_sr126.dat"}, {"test_data/pucch_processor_format2_test_csi1126.dat"}, {"test_data/pucch_processor_format2_test_csi2126.dat"}}, - {{200, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 44, 156, 35, {}, 2, 12, 2, 12450, 199, 39649, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols127.dat"}, {"test_data/pucch_processor_format2_test_harq127.dat"}, {"test_data/pucch_processor_format2_test_sr127.dat"}, {"test_data/pucch_processor_format2_test_csi1127.dat"}, {"test_data/pucch_processor_format2_test_csi2127.dat"}}, - {{274, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 21, 253, 11, {}, 1, 12, 2, 5159, 581, 37055, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols128.dat"}, {"test_data/pucch_processor_format2_test_harq128.dat"}, {"test_data/pucch_processor_format2_test_sr128.dat"}, {"test_data/pucch_processor_format2_test_csi1128.dat"}, {"test_data/pucch_processor_format2_test_csi2128.dat"}}, - {{200, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 16, 184, 8, {}, 1, 12, 2, 25497, 135, 46801, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols129.dat"}, {"test_data/pucch_processor_format2_test_harq129.dat"}, {"test_data/pucch_processor_format2_test_sr129.dat"}, {"test_data/pucch_processor_format2_test_csi1129.dat"}, {"test_data/pucch_processor_format2_test_csi2129.dat"}}, - {{261, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 14, 247, 2, {}, 1, 12, 2, 38473, 984, 54016, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols130.dat"}, {"test_data/pucch_processor_format2_test_harq130.dat"}, {"test_data/pucch_processor_format2_test_sr130.dat"}, {"test_data/pucch_processor_format2_test_csi1130.dat"}, {"test_data/pucch_processor_format2_test_csi2130.dat"}}, - {{270, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 49, 221, 19, {}, 1, 12, 2, 44528, 601, 15001, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols131.dat"}, {"test_data/pucch_processor_format2_test_harq131.dat"}, {"test_data/pucch_processor_format2_test_sr131.dat"}, {"test_data/pucch_processor_format2_test_csi1131.dat"}, {"test_data/pucch_processor_format2_test_csi2131.dat"}}, - {{258, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 144, 114, 71, {}, 8, 12, 2, 19390, 203, 33243, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols132.dat"}, {"test_data/pucch_processor_format2_test_harq132.dat"}, {"test_data/pucch_processor_format2_test_sr132.dat"}, {"test_data/pucch_processor_format2_test_csi1132.dat"}, {"test_data/pucch_processor_format2_test_csi2132.dat"}}, - {{200, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 37, 163, 6, {}, 4, 12, 2, 62928, 484, 60644, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols133.dat"}, {"test_data/pucch_processor_format2_test_harq133.dat"}, {"test_data/pucch_processor_format2_test_sr133.dat"}, {"test_data/pucch_processor_format2_test_csi1133.dat"}, {"test_data/pucch_processor_format2_test_csi2133.dat"}}, - {{243, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 18, 225, 3, {}, 3, 12, 2, 46520, 265, 23087, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols134.dat"}, {"test_data/pucch_processor_format2_test_harq134.dat"}, {"test_data/pucch_processor_format2_test_sr134.dat"}, {"test_data/pucch_processor_format2_test_csi1134.dat"}, {"test_data/pucch_processor_format2_test_csi2134.dat"}}, - {{235, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 176, 59, 38, {}, 2, 12, 2, 4333, 500, 29618, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols135.dat"}, {"test_data/pucch_processor_format2_test_harq135.dat"}, {"test_data/pucch_processor_format2_test_sr135.dat"}, {"test_data/pucch_processor_format2_test_csi1135.dat"}, {"test_data/pucch_processor_format2_test_csi2135.dat"}}, - {{223, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 29, 194, 4, {}, 2, 12, 2, 63160, 265, 49900, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols136.dat"}, {"test_data/pucch_processor_format2_test_harq136.dat"}, {"test_data/pucch_processor_format2_test_sr136.dat"}, {"test_data/pucch_processor_format2_test_csi1136.dat"}, {"test_data/pucch_processor_format2_test_csi2136.dat"}}, - {{179, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 169, 10, 57, {}, 1, 12, 2, 31303, 450, 35077, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols137.dat"}, {"test_data/pucch_processor_format2_test_harq137.dat"}, {"test_data/pucch_processor_format2_test_sr137.dat"}, {"test_data/pucch_processor_format2_test_csi1137.dat"}, {"test_data/pucch_processor_format2_test_csi2137.dat"}}, - {{274, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 8, 266, 0, {}, 8, 12, 2, 320, 181, 31609, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols138.dat"}, {"test_data/pucch_processor_format2_test_harq138.dat"}, {"test_data/pucch_processor_format2_test_sr138.dat"}, {"test_data/pucch_processor_format2_test_csi1138.dat"}, {"test_data/pucch_processor_format2_test_csi2138.dat"}}, - {{103, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 39, 64, 29, {}, 5, 12, 2, 24957, 829, 28329, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols139.dat"}, {"test_data/pucch_processor_format2_test_harq139.dat"}, {"test_data/pucch_processor_format2_test_sr139.dat"}, {"test_data/pucch_processor_format2_test_csi1139.dat"}, {"test_data/pucch_processor_format2_test_csi2139.dat"}}, - {{118, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 114, 4, 58, {}, 3, 12, 2, 34802, 164, 20577, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols140.dat"}, {"test_data/pucch_processor_format2_test_harq140.dat"}, {"test_data/pucch_processor_format2_test_sr140.dat"}, {"test_data/pucch_processor_format2_test_csi1140.dat"}, {"test_data/pucch_processor_format2_test_csi2140.dat"}}, - {{275, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 8, 267, 4, {}, 2, 12, 2, 15483, 350, 46307, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols141.dat"}, {"test_data/pucch_processor_format2_test_harq141.dat"}, {"test_data/pucch_processor_format2_test_sr141.dat"}, {"test_data/pucch_processor_format2_test_csi1141.dat"}, {"test_data/pucch_processor_format2_test_csi2141.dat"}}, - {{72, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 67, 5, 11, {}, 2, 12, 2, 23697, 2, 45712, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols142.dat"}, {"test_data/pucch_processor_format2_test_harq142.dat"}, {"test_data/pucch_processor_format2_test_sr142.dat"}, {"test_data/pucch_processor_format2_test_csi1142.dat"}, {"test_data/pucch_processor_format2_test_csi2142.dat"}}, - {{202, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0}, 70, 132, 50, {}, 2, 12, 2, 18265, 329, 877, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols143.dat"}, {"test_data/pucch_processor_format2_test_harq143.dat"}, {"test_data/pucch_processor_format2_test_sr143.dat"}, {"test_data/pucch_processor_format2_test_csi1143.dat"}, {"test_data/pucch_processor_format2_test_csi2143.dat"}}, + {{270, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 121, 149, 114, {}, 3, 0, 1, 59859, 927, 8322, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols0.dat"}, {"test_data/pucch_processor_format2_test_harq0.dat"}, {"test_data/pucch_processor_format2_test_sr0.dat"}, {"test_data/pucch_processor_format2_test_csi10.dat"}, {"test_data/pucch_processor_format2_test_csi20.dat"}}, + {{179, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 2, 177, 0, {}, 2, 0, 1, 33668, 672, 5297, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols1.dat"}, {"test_data/pucch_processor_format2_test_harq1.dat"}, {"test_data/pucch_processor_format2_test_sr1.dat"}, {"test_data/pucch_processor_format2_test_csi11.dat"}, {"test_data/pucch_processor_format2_test_csi21.dat"}}, + {{272, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 74, 198, 10, {}, 1, 0, 1, 26939, 93, 59264, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols2.dat"}, {"test_data/pucch_processor_format2_test_harq2.dat"}, {"test_data/pucch_processor_format2_test_sr2.dat"}, {"test_data/pucch_processor_format2_test_csi12.dat"}, {"test_data/pucch_processor_format2_test_csi22.dat"}}, + {{255, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 23, 232, 12, {}, 1, 0, 1, 1690, 267, 44482, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols3.dat"}, {"test_data/pucch_processor_format2_test_harq3.dat"}, {"test_data/pucch_processor_format2_test_sr3.dat"}, {"test_data/pucch_processor_format2_test_csi13.dat"}, {"test_data/pucch_processor_format2_test_csi23.dat"}}, + {{211, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 4, 207, 0, {}, 1, 0, 1, 24309, 659, 6737, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols4.dat"}, {"test_data/pucch_processor_format2_test_harq4.dat"}, {"test_data/pucch_processor_format2_test_sr4.dat"}, {"test_data/pucch_processor_format2_test_csi14.dat"}, {"test_data/pucch_processor_format2_test_csi24.dat"}}, + {{240, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 150, 90, 65, {}, 1, 0, 1, 15748, 737, 64333, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols5.dat"}, {"test_data/pucch_processor_format2_test_harq5.dat"}, {"test_data/pucch_processor_format2_test_sr5.dat"}, {"test_data/pucch_processor_format2_test_csi15.dat"}, {"test_data/pucch_processor_format2_test_csi25.dat"}}, + {{236, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 10, 226, 2, {}, 6, 0, 1, 2451, 438, 33823, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols6.dat"}, {"test_data/pucch_processor_format2_test_harq6.dat"}, {"test_data/pucch_processor_format2_test_sr6.dat"}, {"test_data/pucch_processor_format2_test_csi16.dat"}, {"test_data/pucch_processor_format2_test_csi26.dat"}}, + {{255, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 42, 213, 10, {}, 3, 0, 1, 8944, 122, 9154, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols7.dat"}, {"test_data/pucch_processor_format2_test_harq7.dat"}, {"test_data/pucch_processor_format2_test_sr7.dat"}, {"test_data/pucch_processor_format2_test_csi17.dat"}, {"test_data/pucch_processor_format2_test_csi27.dat"}}, + {{204, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 44, 160, 25, {}, 2, 0, 1, 54385, 199, 6301, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols8.dat"}, {"test_data/pucch_processor_format2_test_harq8.dat"}, {"test_data/pucch_processor_format2_test_sr8.dat"}, {"test_data/pucch_processor_format2_test_csi18.dat"}, {"test_data/pucch_processor_format2_test_csi28.dat"}}, + {{202, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 25, 177, 3, {}, 2, 0, 1, 58266, 687, 56663, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols9.dat"}, {"test_data/pucch_processor_format2_test_harq9.dat"}, {"test_data/pucch_processor_format2_test_sr9.dat"}, {"test_data/pucch_processor_format2_test_csi19.dat"}, {"test_data/pucch_processor_format2_test_csi29.dat"}}, + {{112, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 1, 111, 0, {}, 1, 0, 1, 9888, 421, 32215, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols10.dat"}, {"test_data/pucch_processor_format2_test_harq10.dat"}, {"test_data/pucch_processor_format2_test_sr10.dat"}, {"test_data/pucch_processor_format2_test_csi110.dat"}, {"test_data/pucch_processor_format2_test_csi210.dat"}}, + {{153, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 67, 86, 66, {}, 1, 0, 1, 15109, 717, 15513, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols11.dat"}, {"test_data/pucch_processor_format2_test_harq11.dat"}, {"test_data/pucch_processor_format2_test_sr11.dat"}, {"test_data/pucch_processor_format2_test_csi111.dat"}, {"test_data/pucch_processor_format2_test_csi211.dat"}}, + {{160, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 25, 135, 10, {}, 8, 0, 1, 54327, 184, 40825, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols12.dat"}, {"test_data/pucch_processor_format2_test_harq12.dat"}, {"test_data/pucch_processor_format2_test_sr12.dat"}, {"test_data/pucch_processor_format2_test_csi112.dat"}, {"test_data/pucch_processor_format2_test_csi212.dat"}}, + {{132, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 13, 119, 3, {}, 4, 0, 1, 27473, 935, 36699, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols13.dat"}, {"test_data/pucch_processor_format2_test_harq13.dat"}, {"test_data/pucch_processor_format2_test_sr13.dat"}, {"test_data/pucch_processor_format2_test_csi113.dat"}, {"test_data/pucch_processor_format2_test_csi213.dat"}}, + {{274, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 9, 265, 5, {}, 3, 0, 1, 38155, 658, 17114, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols14.dat"}, {"test_data/pucch_processor_format2_test_harq14.dat"}, {"test_data/pucch_processor_format2_test_sr14.dat"}, {"test_data/pucch_processor_format2_test_csi114.dat"}, {"test_data/pucch_processor_format2_test_csi214.dat"}}, + {{275, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 11, 264, 9, {}, 2, 0, 1, 26174, 532, 48633, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols15.dat"}, {"test_data/pucch_processor_format2_test_harq15.dat"}, {"test_data/pucch_processor_format2_test_sr15.dat"}, {"test_data/pucch_processor_format2_test_csi115.dat"}, {"test_data/pucch_processor_format2_test_csi215.dat"}}, + {{254, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 6, 248, 4, {}, 2, 0, 1, 28450, 232, 61287, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols16.dat"}, {"test_data/pucch_processor_format2_test_harq16.dat"}, {"test_data/pucch_processor_format2_test_sr16.dat"}, {"test_data/pucch_processor_format2_test_csi116.dat"}, {"test_data/pucch_processor_format2_test_csi216.dat"}}, + {{138, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 30, 108, 2, {}, 1, 0, 1, 9404, 226, 33921, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols17.dat"}, {"test_data/pucch_processor_format2_test_harq17.dat"}, {"test_data/pucch_processor_format2_test_sr17.dat"}, {"test_data/pucch_processor_format2_test_csi117.dat"}, {"test_data/pucch_processor_format2_test_csi217.dat"}}, + {{220, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 22, 198, 6, {}, 4, 0, 1, 36362, 879, 46248, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols18.dat"}, {"test_data/pucch_processor_format2_test_harq18.dat"}, {"test_data/pucch_processor_format2_test_sr18.dat"}, {"test_data/pucch_processor_format2_test_csi118.dat"}, {"test_data/pucch_processor_format2_test_csi218.dat"}}, + {{257, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 42, 215, 25, {}, 2, 0, 1, 26261, 374, 25653, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols19.dat"}, {"test_data/pucch_processor_format2_test_harq19.dat"}, {"test_data/pucch_processor_format2_test_sr19.dat"}, {"test_data/pucch_processor_format2_test_csi119.dat"}, {"test_data/pucch_processor_format2_test_csi219.dat"}}, + {{166, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 162, 4, 106, {}, 1, 0, 1, 9725, 276, 34041, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols20.dat"}, {"test_data/pucch_processor_format2_test_harq20.dat"}, {"test_data/pucch_processor_format2_test_sr20.dat"}, {"test_data/pucch_processor_format2_test_csi120.dat"}, {"test_data/pucch_processor_format2_test_csi220.dat"}}, + {{267, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 146, 121, 135, {}, 1, 0, 1, 62394, 455, 54004, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols21.dat"}, {"test_data/pucch_processor_format2_test_harq21.dat"}, {"test_data/pucch_processor_format2_test_sr21.dat"}, {"test_data/pucch_processor_format2_test_csi121.dat"}, {"test_data/pucch_processor_format2_test_csi221.dat"}}, + {{186, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 39, 147, 27, {}, 1, 0, 1, 18608, 241, 49515, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols22.dat"}, {"test_data/pucch_processor_format2_test_harq22.dat"}, {"test_data/pucch_processor_format2_test_sr22.dat"}, {"test_data/pucch_processor_format2_test_csi122.dat"}, {"test_data/pucch_processor_format2_test_csi222.dat"}}, + {{93, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 42, 51, 26, {}, 1, 0, 1, 37270, 812, 28973, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols23.dat"}, {"test_data/pucch_processor_format2_test_harq23.dat"}, {"test_data/pucch_processor_format2_test_sr23.dat"}, {"test_data/pucch_processor_format2_test_csi123.dat"}, {"test_data/pucch_processor_format2_test_csi223.dat"}}, + {{196, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 103, 93, 41, {}, 7, 0, 1, 55575, 193, 39172, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols24.dat"}, {"test_data/pucch_processor_format2_test_harq24.dat"}, {"test_data/pucch_processor_format2_test_sr24.dat"}, {"test_data/pucch_processor_format2_test_csi124.dat"}, {"test_data/pucch_processor_format2_test_csi224.dat"}}, + {{171, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 7, 164, 0, {}, 4, 0, 1, 7029, 453, 3528, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols25.dat"}, {"test_data/pucch_processor_format2_test_harq25.dat"}, {"test_data/pucch_processor_format2_test_sr25.dat"}, {"test_data/pucch_processor_format2_test_csi125.dat"}, {"test_data/pucch_processor_format2_test_csi225.dat"}}, + {{133, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 23, 110, 21, {}, 2, 0, 1, 35634, 281, 49043, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols26.dat"}, {"test_data/pucch_processor_format2_test_harq26.dat"}, {"test_data/pucch_processor_format2_test_sr26.dat"}, {"test_data/pucch_processor_format2_test_csi126.dat"}, {"test_data/pucch_processor_format2_test_csi226.dat"}}, + {{209, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 7, 202, 3, {}, 2, 0, 1, 55822, 880, 40243, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols27.dat"}, {"test_data/pucch_processor_format2_test_harq27.dat"}, {"test_data/pucch_processor_format2_test_sr27.dat"}, {"test_data/pucch_processor_format2_test_csi127.dat"}, {"test_data/pucch_processor_format2_test_csi227.dat"}}, + {{240, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 40, 200, 28, {}, 2, 0, 1, 28246, 842, 37833, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols28.dat"}, {"test_data/pucch_processor_format2_test_harq28.dat"}, {"test_data/pucch_processor_format2_test_sr28.dat"}, {"test_data/pucch_processor_format2_test_csi128.dat"}, {"test_data/pucch_processor_format2_test_csi228.dat"}}, + {{126, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 14, 112, 1, {}, 1, 0, 1, 35576, 903, 7938, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols29.dat"}, {"test_data/pucch_processor_format2_test_harq29.dat"}, {"test_data/pucch_processor_format2_test_sr29.dat"}, {"test_data/pucch_processor_format2_test_csi129.dat"}, {"test_data/pucch_processor_format2_test_csi229.dat"}}, + {{209, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 14, 195, 1, {}, 8, 0, 1, 13369, 192, 56676, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols30.dat"}, {"test_data/pucch_processor_format2_test_harq30.dat"}, {"test_data/pucch_processor_format2_test_sr30.dat"}, {"test_data/pucch_processor_format2_test_csi130.dat"}, {"test_data/pucch_processor_format2_test_csi230.dat"}}, + {{52, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 38, 14, 8, {}, 5, 0, 1, 21283, 829, 63093, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols31.dat"}, {"test_data/pucch_processor_format2_test_harq31.dat"}, {"test_data/pucch_processor_format2_test_sr31.dat"}, {"test_data/pucch_processor_format2_test_csi131.dat"}, {"test_data/pucch_processor_format2_test_csi231.dat"}}, + {{266, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 266, 0, 242, {}, 3, 0, 1, 44198, 80, 45330, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols32.dat"}, {"test_data/pucch_processor_format2_test_harq32.dat"}, {"test_data/pucch_processor_format2_test_sr32.dat"}, {"test_data/pucch_processor_format2_test_csi132.dat"}, {"test_data/pucch_processor_format2_test_csi232.dat"}}, + {{261, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 73, 188, 12, {}, 2, 0, 1, 10391, 805, 12180, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols33.dat"}, {"test_data/pucch_processor_format2_test_harq33.dat"}, {"test_data/pucch_processor_format2_test_sr33.dat"}, {"test_data/pucch_processor_format2_test_csi133.dat"}, {"test_data/pucch_processor_format2_test_csi233.dat"}}, + {{236, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 2, 234, 0, {}, 2, 0, 1, 38320, 311, 14900, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols34.dat"}, {"test_data/pucch_processor_format2_test_harq34.dat"}, {"test_data/pucch_processor_format2_test_sr34.dat"}, {"test_data/pucch_processor_format2_test_csi134.dat"}, {"test_data/pucch_processor_format2_test_csi234.dat"}}, + {{241, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 110, 131, 14, {}, 2, 0, 1, 47399, 284, 17489, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols35.dat"}, {"test_data/pucch_processor_format2_test_harq35.dat"}, {"test_data/pucch_processor_format2_test_sr35.dat"}, {"test_data/pucch_processor_format2_test_csi135.dat"}, {"test_data/pucch_processor_format2_test_csi235.dat"}}, + {{111, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 20, 91, 4, {}, 6, 0, 1, 16799, 419, 63175, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols36.dat"}, {"test_data/pucch_processor_format2_test_harq36.dat"}, {"test_data/pucch_processor_format2_test_sr36.dat"}, {"test_data/pucch_processor_format2_test_csi136.dat"}, {"test_data/pucch_processor_format2_test_csi236.dat"}}, + {{233, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 5, 228, 2, {}, 3, 0, 1, 24819, 308, 29994, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols37.dat"}, {"test_data/pucch_processor_format2_test_harq37.dat"}, {"test_data/pucch_processor_format2_test_sr37.dat"}, {"test_data/pucch_processor_format2_test_csi137.dat"}, {"test_data/pucch_processor_format2_test_csi237.dat"}}, + {{127, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 28, 99, 7, {}, 2, 0, 1, 44183, 563, 35449, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols38.dat"}, {"test_data/pucch_processor_format2_test_harq38.dat"}, {"test_data/pucch_processor_format2_test_sr38.dat"}, {"test_data/pucch_processor_format2_test_csi138.dat"}, {"test_data/pucch_processor_format2_test_csi238.dat"}}, + {{261, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 15, 246, 3, {}, 2, 0, 1, 62434, 459, 54093, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols39.dat"}, {"test_data/pucch_processor_format2_test_harq39.dat"}, {"test_data/pucch_processor_format2_test_sr39.dat"}, {"test_data/pucch_processor_format2_test_csi139.dat"}, {"test_data/pucch_processor_format2_test_csi239.dat"}}, + {{193, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 175, 18, 52, {}, 1, 0, 1, 21175, 930, 41979, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols40.dat"}, {"test_data/pucch_processor_format2_test_harq40.dat"}, {"test_data/pucch_processor_format2_test_sr40.dat"}, {"test_data/pucch_processor_format2_test_csi140.dat"}, {"test_data/pucch_processor_format2_test_csi240.dat"}}, + {{172, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 15, 157, 6, {}, 1, 0, 1, 11699, 813, 17077, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols41.dat"}, {"test_data/pucch_processor_format2_test_harq41.dat"}, {"test_data/pucch_processor_format2_test_sr41.dat"}, {"test_data/pucch_processor_format2_test_csi141.dat"}, {"test_data/pucch_processor_format2_test_csi241.dat"}}, + {{270, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 18, 252, 6, {}, 9, 0, 1, 13935, 872, 8699, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols42.dat"}, {"test_data/pucch_processor_format2_test_harq42.dat"}, {"test_data/pucch_processor_format2_test_sr42.dat"}, {"test_data/pucch_processor_format2_test_csi142.dat"}, {"test_data/pucch_processor_format2_test_csi242.dat"}}, + {{257, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 35, 222, 25, {}, 5, 0, 1, 29230, 87, 21224, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols43.dat"}, {"test_data/pucch_processor_format2_test_harq43.dat"}, {"test_data/pucch_processor_format2_test_sr43.dat"}, {"test_data/pucch_processor_format2_test_csi143.dat"}, {"test_data/pucch_processor_format2_test_csi243.dat"}}, + {{191, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 189, 2, 43, {}, 3, 0, 1, 10841, 583, 49204, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols44.dat"}, {"test_data/pucch_processor_format2_test_harq44.dat"}, {"test_data/pucch_processor_format2_test_sr44.dat"}, {"test_data/pucch_processor_format2_test_csi144.dat"}, {"test_data/pucch_processor_format2_test_csi244.dat"}}, + {{257, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 129, 128, 41, {}, 2, 0, 1, 24178, 662, 24573, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols45.dat"}, {"test_data/pucch_processor_format2_test_harq45.dat"}, {"test_data/pucch_processor_format2_test_sr45.dat"}, {"test_data/pucch_processor_format2_test_csi145.dat"}, {"test_data/pucch_processor_format2_test_csi245.dat"}}, + {{92, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 81, 11, 57, {}, 2, 0, 1, 36105, 443, 63513, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols46.dat"}, {"test_data/pucch_processor_format2_test_harq46.dat"}, {"test_data/pucch_processor_format2_test_sr46.dat"}, {"test_data/pucch_processor_format2_test_csi146.dat"}, {"test_data/pucch_processor_format2_test_csi246.dat"}}, + {{275, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 3, 272, 1, {}, 2, 0, 1, 5939, 290, 22047, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols47.dat"}, {"test_data/pucch_processor_format2_test_harq47.dat"}, {"test_data/pucch_processor_format2_test_sr47.dat"}, {"test_data/pucch_processor_format2_test_csi147.dat"}, {"test_data/pucch_processor_format2_test_csi247.dat"}}, + {{238, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 84, 154, 44, {}, 15, 0, 1, 54934, 939, 62642, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols48.dat"}, {"test_data/pucch_processor_format2_test_harq48.dat"}, {"test_data/pucch_processor_format2_test_sr48.dat"}, {"test_data/pucch_processor_format2_test_csi148.dat"}, {"test_data/pucch_processor_format2_test_csi248.dat"}}, + {{250, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 221, 29, 162, {}, 8, 0, 1, 21399, 471, 17728, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols49.dat"}, {"test_data/pucch_processor_format2_test_harq49.dat"}, {"test_data/pucch_processor_format2_test_sr49.dat"}, {"test_data/pucch_processor_format2_test_csi149.dat"}, {"test_data/pucch_processor_format2_test_csi249.dat"}}, + {{172, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 139, 33, 132, {}, 5, 0, 1, 46637, 697, 38340, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols50.dat"}, {"test_data/pucch_processor_format2_test_harq50.dat"}, {"test_data/pucch_processor_format2_test_sr50.dat"}, {"test_data/pucch_processor_format2_test_csi150.dat"}, {"test_data/pucch_processor_format2_test_csi250.dat"}}, + {{252, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 19, 233, 11, {}, 4, 0, 1, 33295, 528, 46631, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols51.dat"}, {"test_data/pucch_processor_format2_test_harq51.dat"}, {"test_data/pucch_processor_format2_test_sr51.dat"}, {"test_data/pucch_processor_format2_test_csi151.dat"}, {"test_data/pucch_processor_format2_test_csi251.dat"}}, + {{239, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 182, 57, 71, {}, 3, 0, 1, 48818, 492, 34158, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols52.dat"}, {"test_data/pucch_processor_format2_test_harq52.dat"}, {"test_data/pucch_processor_format2_test_sr52.dat"}, {"test_data/pucch_processor_format2_test_csi152.dat"}, {"test_data/pucch_processor_format2_test_csi252.dat"}}, + {{267, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 9, 258, 2, {}, 2, 0, 1, 6267, 127, 6027, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols53.dat"}, {"test_data/pucch_processor_format2_test_harq53.dat"}, {"test_data/pucch_processor_format2_test_sr53.dat"}, {"test_data/pucch_processor_format2_test_csi153.dat"}, {"test_data/pucch_processor_format2_test_csi253.dat"}}, + {{128, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 45, 83, 21, {}, 7, 0, 1, 34214, 750, 57596, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols54.dat"}, {"test_data/pucch_processor_format2_test_harq54.dat"}, {"test_data/pucch_processor_format2_test_sr54.dat"}, {"test_data/pucch_processor_format2_test_csi154.dat"}, {"test_data/pucch_processor_format2_test_csi254.dat"}}, + {{45, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 9, 36, 3, {}, 4, 0, 1, 31839, 686, 11053, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols55.dat"}, {"test_data/pucch_processor_format2_test_harq55.dat"}, {"test_data/pucch_processor_format2_test_sr55.dat"}, {"test_data/pucch_processor_format2_test_csi155.dat"}, {"test_data/pucch_processor_format2_test_csi255.dat"}}, + {{254, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 66, 188, 4, {}, 2, 0, 1, 2346, 354, 2849, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols56.dat"}, {"test_data/pucch_processor_format2_test_harq56.dat"}, {"test_data/pucch_processor_format2_test_sr56.dat"}, {"test_data/pucch_processor_format2_test_csi156.dat"}, {"test_data/pucch_processor_format2_test_csi256.dat"}}, + {{257, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 15, 242, 1, {}, 2, 0, 1, 36370, 253, 23748, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols57.dat"}, {"test_data/pucch_processor_format2_test_harq57.dat"}, {"test_data/pucch_processor_format2_test_sr57.dat"}, {"test_data/pucch_processor_format2_test_csi157.dat"}, {"test_data/pucch_processor_format2_test_csi257.dat"}}, + {{148, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 104, 44, 96, {}, 2, 0, 1, 55097, 252, 18256, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols58.dat"}, {"test_data/pucch_processor_format2_test_harq58.dat"}, {"test_data/pucch_processor_format2_test_sr58.dat"}, {"test_data/pucch_processor_format2_test_csi158.dat"}, {"test_data/pucch_processor_format2_test_csi258.dat"}}, + {{238, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 18, 220, 8, {}, 1, 0, 1, 38469, 515, 43453, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols59.dat"}, {"test_data/pucch_processor_format2_test_harq59.dat"}, {"test_data/pucch_processor_format2_test_sr59.dat"}, {"test_data/pucch_processor_format2_test_csi159.dat"}, {"test_data/pucch_processor_format2_test_csi259.dat"}}, + {{214, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 60, 154, 32, {}, 15, 0, 1, 13313, 773, 35961, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols60.dat"}, {"test_data/pucch_processor_format2_test_harq60.dat"}, {"test_data/pucch_processor_format2_test_sr60.dat"}, {"test_data/pucch_processor_format2_test_csi160.dat"}, {"test_data/pucch_processor_format2_test_csi260.dat"}}, + {{254, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 8, 246, 0, {}, 8, 0, 1, 58808, 160, 16224, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols61.dat"}, {"test_data/pucch_processor_format2_test_harq61.dat"}, {"test_data/pucch_processor_format2_test_sr61.dat"}, {"test_data/pucch_processor_format2_test_csi161.dat"}, {"test_data/pucch_processor_format2_test_csi261.dat"}}, + {{274, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 16, 258, 3, {}, 5, 0, 1, 36765, 320, 8128, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols62.dat"}, {"test_data/pucch_processor_format2_test_harq62.dat"}, {"test_data/pucch_processor_format2_test_sr62.dat"}, {"test_data/pucch_processor_format2_test_csi162.dat"}, {"test_data/pucch_processor_format2_test_csi262.dat"}}, + {{161, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 51, 110, 40, {}, 4, 0, 1, 3566, 1021, 47920, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols63.dat"}, {"test_data/pucch_processor_format2_test_harq63.dat"}, {"test_data/pucch_processor_format2_test_sr63.dat"}, {"test_data/pucch_processor_format2_test_csi163.dat"}, {"test_data/pucch_processor_format2_test_csi263.dat"}}, + {{253, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 29, 224, 9, {}, 3, 0, 1, 38055, 896, 62475, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols64.dat"}, {"test_data/pucch_processor_format2_test_harq64.dat"}, {"test_data/pucch_processor_format2_test_sr64.dat"}, {"test_data/pucch_processor_format2_test_csi164.dat"}, {"test_data/pucch_processor_format2_test_csi264.dat"}}, + {{191, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 49, 142, 14, {}, 2, 0, 1, 18964, 757, 62674, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols65.dat"}, {"test_data/pucch_processor_format2_test_harq65.dat"}, {"test_data/pucch_processor_format2_test_sr65.dat"}, {"test_data/pucch_processor_format2_test_csi165.dat"}, {"test_data/pucch_processor_format2_test_csi265.dat"}}, + {{227, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 175, 52, 52, {}, 16, 0, 1, 49733, 501, 45797, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols66.dat"}, {"test_data/pucch_processor_format2_test_harq66.dat"}, {"test_data/pucch_processor_format2_test_sr66.dat"}, {"test_data/pucch_processor_format2_test_csi166.dat"}, {"test_data/pucch_processor_format2_test_csi266.dat"}}, + {{223, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 163, 60, 32, {}, 9, 0, 1, 22580, 560, 37393, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols67.dat"}, {"test_data/pucch_processor_format2_test_harq67.dat"}, {"test_data/pucch_processor_format2_test_sr67.dat"}, {"test_data/pucch_processor_format2_test_csi167.dat"}, {"test_data/pucch_processor_format2_test_csi267.dat"}}, + {{160, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 135, 25, 37, {}, 5, 0, 1, 41761, 167, 32209, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols68.dat"}, {"test_data/pucch_processor_format2_test_harq68.dat"}, {"test_data/pucch_processor_format2_test_sr68.dat"}, {"test_data/pucch_processor_format2_test_csi168.dat"}, {"test_data/pucch_processor_format2_test_csi268.dat"}}, + {{275, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 8, 267, 4, {}, 4, 0, 1, 39029, 956, 17423, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols69.dat"}, {"test_data/pucch_processor_format2_test_harq69.dat"}, {"test_data/pucch_processor_format2_test_sr69.dat"}, {"test_data/pucch_processor_format2_test_csi169.dat"}, {"test_data/pucch_processor_format2_test_csi269.dat"}}, + {{206, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 44, 162, 7, {}, 3, 0, 1, 59531, 784, 50152, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols70.dat"}, {"test_data/pucch_processor_format2_test_harq70.dat"}, {"test_data/pucch_processor_format2_test_sr70.dat"}, {"test_data/pucch_processor_format2_test_csi170.dat"}, {"test_data/pucch_processor_format2_test_csi270.dat"}}, + {{246, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 26, 220, 12, {}, 3, 0, 1, 9715, 187, 7721, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols71.dat"}, {"test_data/pucch_processor_format2_test_harq71.dat"}, {"test_data/pucch_processor_format2_test_sr71.dat"}, {"test_data/pucch_processor_format2_test_csi171.dat"}, {"test_data/pucch_processor_format2_test_csi271.dat"}}, + {{179, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 91, 88, 79, {}, 2, 12, 2, 57848, 1018, 18463, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols72.dat"}, {"test_data/pucch_processor_format2_test_harq72.dat"}, {"test_data/pucch_processor_format2_test_sr72.dat"}, {"test_data/pucch_processor_format2_test_csi172.dat"}, {"test_data/pucch_processor_format2_test_csi272.dat"}}, + {{244, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 20, 224, 1, {}, 1, 12, 2, 42308, 260, 23563, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols73.dat"}, {"test_data/pucch_processor_format2_test_harq73.dat"}, {"test_data/pucch_processor_format2_test_sr73.dat"}, {"test_data/pucch_processor_format2_test_csi173.dat"}, {"test_data/pucch_processor_format2_test_csi273.dat"}}, + {{255, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 22, 233, 4, {}, 1, 12, 2, 19495, 427, 45387, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols74.dat"}, {"test_data/pucch_processor_format2_test_harq74.dat"}, {"test_data/pucch_processor_format2_test_sr74.dat"}, {"test_data/pucch_processor_format2_test_csi174.dat"}, {"test_data/pucch_processor_format2_test_csi274.dat"}}, + {{241, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 71, 170, 39, {}, 1, 12, 2, 155, 493, 28610, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols75.dat"}, {"test_data/pucch_processor_format2_test_harq75.dat"}, {"test_data/pucch_processor_format2_test_sr75.dat"}, {"test_data/pucch_processor_format2_test_csi175.dat"}, {"test_data/pucch_processor_format2_test_csi275.dat"}}, + {{261, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 36, 225, 0, {}, 1, 12, 2, 4164, 857, 20310, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols76.dat"}, {"test_data/pucch_processor_format2_test_harq76.dat"}, {"test_data/pucch_processor_format2_test_sr76.dat"}, {"test_data/pucch_processor_format2_test_csi176.dat"}, {"test_data/pucch_processor_format2_test_csi276.dat"}}, + {{250, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 77, 173, 75, {}, 1, 12, 2, 23315, 779, 20491, 3, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols77.dat"}, {"test_data/pucch_processor_format2_test_harq77.dat"}, {"test_data/pucch_processor_format2_test_sr77.dat"}, {"test_data/pucch_processor_format2_test_csi177.dat"}, {"test_data/pucch_processor_format2_test_csi277.dat"}}, + {{188, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 28, 160, 21, {}, 3, 12, 2, 8862, 300, 9265, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols78.dat"}, {"test_data/pucch_processor_format2_test_harq78.dat"}, {"test_data/pucch_processor_format2_test_sr78.dat"}, {"test_data/pucch_processor_format2_test_csi178.dat"}, {"test_data/pucch_processor_format2_test_csi278.dat"}}, + {{103, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 78, 25, 29, {}, 2, 12, 2, 20789, 263, 45422, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols79.dat"}, {"test_data/pucch_processor_format2_test_harq79.dat"}, {"test_data/pucch_processor_format2_test_sr79.dat"}, {"test_data/pucch_processor_format2_test_csi179.dat"}, {"test_data/pucch_processor_format2_test_csi279.dat"}}, + {{214, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 5, 209, 3, {}, 1, 12, 2, 40506, 731, 52327, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols80.dat"}, {"test_data/pucch_processor_format2_test_harq80.dat"}, {"test_data/pucch_processor_format2_test_sr80.dat"}, {"test_data/pucch_processor_format2_test_csi180.dat"}, {"test_data/pucch_processor_format2_test_csi280.dat"}}, + {{52, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 6, 46, 5, {}, 1, 12, 2, 13500, 67, 38852, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols81.dat"}, {"test_data/pucch_processor_format2_test_harq81.dat"}, {"test_data/pucch_processor_format2_test_sr81.dat"}, {"test_data/pucch_processor_format2_test_csi181.dat"}, {"test_data/pucch_processor_format2_test_csi281.dat"}}, + {{116, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 51, 65, 29, {}, 1, 12, 2, 44138, 322, 51497, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols82.dat"}, {"test_data/pucch_processor_format2_test_harq82.dat"}, {"test_data/pucch_processor_format2_test_sr82.dat"}, {"test_data/pucch_processor_format2_test_csi182.dat"}, {"test_data/pucch_processor_format2_test_csi282.dat"}}, + {{87, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 61, 26, 36, {}, 1, 12, 2, 1909, 982, 48024, 3, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols83.dat"}, {"test_data/pucch_processor_format2_test_harq83.dat"}, {"test_data/pucch_processor_format2_test_sr83.dat"}, {"test_data/pucch_processor_format2_test_csi183.dat"}, {"test_data/pucch_processor_format2_test_csi283.dat"}}, + {{260, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 8, 252, 4, {}, 4, 12, 2, 27959, 506, 3755, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols84.dat"}, {"test_data/pucch_processor_format2_test_harq84.dat"}, {"test_data/pucch_processor_format2_test_sr84.dat"}, {"test_data/pucch_processor_format2_test_csi184.dat"}, {"test_data/pucch_processor_format2_test_csi284.dat"}}, + {{127, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 27, 100, 10, {}, 2, 12, 2, 18196, 350, 45861, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols85.dat"}, {"test_data/pucch_processor_format2_test_harq85.dat"}, {"test_data/pucch_processor_format2_test_sr85.dat"}, {"test_data/pucch_processor_format2_test_csi185.dat"}, {"test_data/pucch_processor_format2_test_csi285.dat"}}, + {{229, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 168, 61, 147, {}, 2, 12, 2, 38881, 803, 23698, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols86.dat"}, {"test_data/pucch_processor_format2_test_harq86.dat"}, {"test_data/pucch_processor_format2_test_sr86.dat"}, {"test_data/pucch_processor_format2_test_csi186.dat"}, {"test_data/pucch_processor_format2_test_csi286.dat"}}, + {{251, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 4, 247, 0, {}, 1, 12, 2, 25540, 862, 46389, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols87.dat"}, {"test_data/pucch_processor_format2_test_harq87.dat"}, {"test_data/pucch_processor_format2_test_sr87.dat"}, {"test_data/pucch_processor_format2_test_csi187.dat"}, {"test_data/pucch_processor_format2_test_csi287.dat"}}, + {{92, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 64, 28, 52, {}, 1, 12, 2, 52874, 461, 6126, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols88.dat"}, {"test_data/pucch_processor_format2_test_harq88.dat"}, {"test_data/pucch_processor_format2_test_sr88.dat"}, {"test_data/pucch_processor_format2_test_csi188.dat"}, {"test_data/pucch_processor_format2_test_csi288.dat"}}, + {{116, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 15, 101, 3, {}, 1, 12, 2, 65134, 941, 18603, 3, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols89.dat"}, {"test_data/pucch_processor_format2_test_harq89.dat"}, {"test_data/pucch_processor_format2_test_sr89.dat"}, {"test_data/pucch_processor_format2_test_csi189.dat"}, {"test_data/pucch_processor_format2_test_csi289.dat"}}, + {{258, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 21, 237, 1, {}, 2, 12, 2, 47559, 611, 46293, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols90.dat"}, {"test_data/pucch_processor_format2_test_harq90.dat"}, {"test_data/pucch_processor_format2_test_sr90.dat"}, {"test_data/pucch_processor_format2_test_csi190.dat"}, {"test_data/pucch_processor_format2_test_csi290.dat"}}, + {{152, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 81, 71, 10, {}, 1, 12, 2, 25684, 437, 19093, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols91.dat"}, {"test_data/pucch_processor_format2_test_harq91.dat"}, {"test_data/pucch_processor_format2_test_sr91.dat"}, {"test_data/pucch_processor_format2_test_csi191.dat"}, {"test_data/pucch_processor_format2_test_csi291.dat"}}, + {{259, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 16, 243, 10, {}, 1, 12, 2, 13983, 834, 3723, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols92.dat"}, {"test_data/pucch_processor_format2_test_harq92.dat"}, {"test_data/pucch_processor_format2_test_sr92.dat"}, {"test_data/pucch_processor_format2_test_csi192.dat"}, {"test_data/pucch_processor_format2_test_csi292.dat"}}, + {{233, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 46, 187, 12, {}, 1, 12, 2, 29315, 329, 9184, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols93.dat"}, {"test_data/pucch_processor_format2_test_harq93.dat"}, {"test_data/pucch_processor_format2_test_sr93.dat"}, {"test_data/pucch_processor_format2_test_csi193.dat"}, {"test_data/pucch_processor_format2_test_csi293.dat"}}, + {{116, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 64, 52, 7, {}, 1, 12, 2, 10220, 787, 7772, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols94.dat"}, {"test_data/pucch_processor_format2_test_harq94.dat"}, {"test_data/pucch_processor_format2_test_sr94.dat"}, {"test_data/pucch_processor_format2_test_csi194.dat"}, {"test_data/pucch_processor_format2_test_csi294.dat"}}, + {{188, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 6, 182, 2, {}, 1, 12, 2, 31937, 813, 37507, 3, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols95.dat"}, {"test_data/pucch_processor_format2_test_harq95.dat"}, {"test_data/pucch_processor_format2_test_sr95.dat"}, {"test_data/pucch_processor_format2_test_csi195.dat"}, {"test_data/pucch_processor_format2_test_csi295.dat"}}, + {{274, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 71, 203, 10, {}, 4, 12, 2, 57655, 220, 3123, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols96.dat"}, {"test_data/pucch_processor_format2_test_harq96.dat"}, {"test_data/pucch_processor_format2_test_sr96.dat"}, {"test_data/pucch_processor_format2_test_csi196.dat"}, {"test_data/pucch_processor_format2_test_csi296.dat"}}, + {{248, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 144, 104, 26, {}, 2, 12, 2, 19211, 398, 13047, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols97.dat"}, {"test_data/pucch_processor_format2_test_harq97.dat"}, {"test_data/pucch_processor_format2_test_sr97.dat"}, {"test_data/pucch_processor_format2_test_csi197.dat"}, {"test_data/pucch_processor_format2_test_csi297.dat"}}, + {{256, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 18, 238, 3, {}, 1, 12, 2, 53925, 235, 40987, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols98.dat"}, {"test_data/pucch_processor_format2_test_harq98.dat"}, {"test_data/pucch_processor_format2_test_sr98.dat"}, {"test_data/pucch_processor_format2_test_csi198.dat"}, {"test_data/pucch_processor_format2_test_csi298.dat"}}, + {{247, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 167, 80, 102, {}, 1, 12, 2, 61245, 475, 40594, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols99.dat"}, {"test_data/pucch_processor_format2_test_harq99.dat"}, {"test_data/pucch_processor_format2_test_sr99.dat"}, {"test_data/pucch_processor_format2_test_csi199.dat"}, {"test_data/pucch_processor_format2_test_csi299.dat"}}, + {{253, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 9, 244, 8, {}, 1, 12, 2, 40107, 328, 31753, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols100.dat"}, {"test_data/pucch_processor_format2_test_harq100.dat"}, {"test_data/pucch_processor_format2_test_sr100.dat"}, {"test_data/pucch_processor_format2_test_csi1100.dat"}, {"test_data/pucch_processor_format2_test_csi2100.dat"}}, + {{248, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 158, 90, 156, {}, 1, 12, 2, 26387, 585, 54194, 3, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols101.dat"}, {"test_data/pucch_processor_format2_test_harq101.dat"}, {"test_data/pucch_processor_format2_test_sr101.dat"}, {"test_data/pucch_processor_format2_test_csi1101.dat"}, {"test_data/pucch_processor_format2_test_csi2101.dat"}}, + {{143, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 57, 86, 22, {}, 4, 12, 2, 7925, 99, 40328, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols102.dat"}, {"test_data/pucch_processor_format2_test_harq102.dat"}, {"test_data/pucch_processor_format2_test_sr102.dat"}, {"test_data/pucch_processor_format2_test_csi1102.dat"}, {"test_data/pucch_processor_format2_test_csi2102.dat"}}, + {{258, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 34, 224, 7, {}, 3, 12, 2, 22096, 526, 21708, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols103.dat"}, {"test_data/pucch_processor_format2_test_harq103.dat"}, {"test_data/pucch_processor_format2_test_sr103.dat"}, {"test_data/pucch_processor_format2_test_csi1103.dat"}, {"test_data/pucch_processor_format2_test_csi2103.dat"}}, + {{193, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 94, 99, 54, {}, 2, 12, 2, 56501, 816, 8583, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols104.dat"}, {"test_data/pucch_processor_format2_test_harq104.dat"}, {"test_data/pucch_processor_format2_test_sr104.dat"}, {"test_data/pucch_processor_format2_test_csi1104.dat"}, {"test_data/pucch_processor_format2_test_csi2104.dat"}}, + {{257, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 115, 142, 89, {}, 1, 12, 2, 25716, 82, 47212, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols105.dat"}, {"test_data/pucch_processor_format2_test_harq105.dat"}, {"test_data/pucch_processor_format2_test_sr105.dat"}, {"test_data/pucch_processor_format2_test_csi1105.dat"}, {"test_data/pucch_processor_format2_test_csi2105.dat"}}, + {{107, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 23, 84, 12, {}, 1, 12, 2, 47909, 7, 34598, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols106.dat"}, {"test_data/pucch_processor_format2_test_harq106.dat"}, {"test_data/pucch_processor_format2_test_sr106.dat"}, {"test_data/pucch_processor_format2_test_csi1106.dat"}, {"test_data/pucch_processor_format2_test_csi2106.dat"}}, + {{75, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 41, 34, 8, {}, 1, 12, 2, 45350, 566, 28155, 3, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols107.dat"}, {"test_data/pucch_processor_format2_test_harq107.dat"}, {"test_data/pucch_processor_format2_test_sr107.dat"}, {"test_data/pucch_processor_format2_test_csi1107.dat"}, {"test_data/pucch_processor_format2_test_csi2107.dat"}}, + {{185, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 53, 132, 26, {}, 3, 12, 2, 33440, 813, 19244, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols108.dat"}, {"test_data/pucch_processor_format2_test_harq108.dat"}, {"test_data/pucch_processor_format2_test_sr108.dat"}, {"test_data/pucch_processor_format2_test_csi1108.dat"}, {"test_data/pucch_processor_format2_test_csi2108.dat"}}, + {{153, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 50, 103, 16, {}, 2, 12, 2, 1048, 151, 57611, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols109.dat"}, {"test_data/pucch_processor_format2_test_harq109.dat"}, {"test_data/pucch_processor_format2_test_sr109.dat"}, {"test_data/pucch_processor_format2_test_csi1109.dat"}, {"test_data/pucch_processor_format2_test_csi2109.dat"}}, + {{177, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 85, 92, 78, {}, 1, 12, 2, 61101, 888, 63984, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols110.dat"}, {"test_data/pucch_processor_format2_test_harq110.dat"}, {"test_data/pucch_processor_format2_test_sr110.dat"}, {"test_data/pucch_processor_format2_test_csi1110.dat"}, {"test_data/pucch_processor_format2_test_csi2110.dat"}}, + {{256, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 40, 216, 6, {}, 1, 12, 2, 31926, 78, 13038, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols111.dat"}, {"test_data/pucch_processor_format2_test_harq111.dat"}, {"test_data/pucch_processor_format2_test_sr111.dat"}, {"test_data/pucch_processor_format2_test_csi1111.dat"}, {"test_data/pucch_processor_format2_test_csi2111.dat"}}, + {{247, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 54, 193, 17, {}, 1, 12, 2, 51688, 519, 54308, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols112.dat"}, {"test_data/pucch_processor_format2_test_harq112.dat"}, {"test_data/pucch_processor_format2_test_sr112.dat"}, {"test_data/pucch_processor_format2_test_csi1112.dat"}, {"test_data/pucch_processor_format2_test_csi2112.dat"}}, + {{184, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 62, 122, 38, {}, 1, 12, 2, 49591, 343, 38109, 7, 0, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols113.dat"}, {"test_data/pucch_processor_format2_test_harq113.dat"}, {"test_data/pucch_processor_format2_test_sr113.dat"}, {"test_data/pucch_processor_format2_test_csi1113.dat"}, {"test_data/pucch_processor_format2_test_csi2113.dat"}}, + {{132, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 15, 117, 2, {}, 5, 12, 2, 36692, 413, 14594, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols114.dat"}, {"test_data/pucch_processor_format2_test_harq114.dat"}, {"test_data/pucch_processor_format2_test_sr114.dat"}, {"test_data/pucch_processor_format2_test_csi1114.dat"}, {"test_data/pucch_processor_format2_test_csi2114.dat"}}, + {{274, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 5, 269, 1, {}, 3, 12, 2, 32375, 84, 3283, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols115.dat"}, {"test_data/pucch_processor_format2_test_harq115.dat"}, {"test_data/pucch_processor_format2_test_sr115.dat"}, {"test_data/pucch_processor_format2_test_csi1115.dat"}, {"test_data/pucch_processor_format2_test_csi2115.dat"}}, + {{234, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 26, 208, 4, {}, 2, 12, 2, 37162, 749, 54429, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols116.dat"}, {"test_data/pucch_processor_format2_test_harq116.dat"}, {"test_data/pucch_processor_format2_test_sr116.dat"}, {"test_data/pucch_processor_format2_test_csi1116.dat"}, {"test_data/pucch_processor_format2_test_csi2116.dat"}}, + {{114, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 52, 62, 26, {}, 1, 12, 2, 33096, 518, 43386, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols117.dat"}, {"test_data/pucch_processor_format2_test_harq117.dat"}, {"test_data/pucch_processor_format2_test_sr117.dat"}, {"test_data/pucch_processor_format2_test_csi1117.dat"}, {"test_data/pucch_processor_format2_test_csi2117.dat"}}, + {{174, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 147, 27, 34, {}, 1, 12, 2, 1603, 325, 17940, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols118.dat"}, {"test_data/pucch_processor_format2_test_harq118.dat"}, {"test_data/pucch_processor_format2_test_sr118.dat"}, {"test_data/pucch_processor_format2_test_csi1118.dat"}, {"test_data/pucch_processor_format2_test_csi2118.dat"}}, + {{178, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 94, 84, 84, {}, 1, 12, 2, 22142, 298, 10803, 7, 0, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols119.dat"}, {"test_data/pucch_processor_format2_test_harq119.dat"}, {"test_data/pucch_processor_format2_test_sr119.dat"}, {"test_data/pucch_processor_format2_test_csi1119.dat"}, {"test_data/pucch_processor_format2_test_csi2119.dat"}}, + {{249, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 44, 205, 4, {}, 8, 12, 2, 30260, 1019, 60217, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols120.dat"}, {"test_data/pucch_processor_format2_test_harq120.dat"}, {"test_data/pucch_processor_format2_test_sr120.dat"}, {"test_data/pucch_processor_format2_test_csi1120.dat"}, {"test_data/pucch_processor_format2_test_csi2120.dat"}}, + {{268, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 4, 264, 0, {}, 4, 12, 2, 11518, 763, 16065, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols121.dat"}, {"test_data/pucch_processor_format2_test_harq121.dat"}, {"test_data/pucch_processor_format2_test_sr121.dat"}, {"test_data/pucch_processor_format2_test_csi1121.dat"}, {"test_data/pucch_processor_format2_test_csi2121.dat"}}, + {{172, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 33, 139, 7, {}, 3, 12, 2, 60786, 450, 18462, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols122.dat"}, {"test_data/pucch_processor_format2_test_harq122.dat"}, {"test_data/pucch_processor_format2_test_sr122.dat"}, {"test_data/pucch_processor_format2_test_csi1122.dat"}, {"test_data/pucch_processor_format2_test_csi2122.dat"}}, + {{233, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 106, 127, 67, {}, 2, 12, 2, 15376, 282, 46839, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols123.dat"}, {"test_data/pucch_processor_format2_test_harq123.dat"}, {"test_data/pucch_processor_format2_test_sr123.dat"}, {"test_data/pucch_processor_format2_test_csi1123.dat"}, {"test_data/pucch_processor_format2_test_csi2123.dat"}}, + {{173, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 9, 164, 5, {}, 2, 12, 2, 18705, 210, 32931, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols124.dat"}, {"test_data/pucch_processor_format2_test_harq124.dat"}, {"test_data/pucch_processor_format2_test_sr124.dat"}, {"test_data/pucch_processor_format2_test_csi1124.dat"}, {"test_data/pucch_processor_format2_test_csi2124.dat"}}, + {{246, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 8, 238, 2, {}, 1, 12, 2, 6786, 906, 53655, 7, 0, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols125.dat"}, {"test_data/pucch_processor_format2_test_harq125.dat"}, {"test_data/pucch_processor_format2_test_sr125.dat"}, {"test_data/pucch_processor_format2_test_csi1125.dat"}, {"test_data/pucch_processor_format2_test_csi2125.dat"}}, + {{270, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 52, 218, 32, {}, 4, 12, 2, 27066, 533, 41115, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols126.dat"}, {"test_data/pucch_processor_format2_test_harq126.dat"}, {"test_data/pucch_processor_format2_test_sr126.dat"}, {"test_data/pucch_processor_format2_test_csi1126.dat"}, {"test_data/pucch_processor_format2_test_csi2126.dat"}}, + {{245, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 89, 156, 86, {}, 2, 12, 2, 9175, 677, 37908, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols127.dat"}, {"test_data/pucch_processor_format2_test_harq127.dat"}, {"test_data/pucch_processor_format2_test_sr127.dat"}, {"test_data/pucch_processor_format2_test_csi1127.dat"}, {"test_data/pucch_processor_format2_test_csi2127.dat"}}, + {{267, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 11, 256, 9, {}, 1, 12, 2, 9692, 212, 661, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols128.dat"}, {"test_data/pucch_processor_format2_test_harq128.dat"}, {"test_data/pucch_processor_format2_test_sr128.dat"}, {"test_data/pucch_processor_format2_test_csi1128.dat"}, {"test_data/pucch_processor_format2_test_csi2128.dat"}}, + {{171, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 115, 56, 112, {}, 1, 12, 2, 13058, 188, 47083, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols129.dat"}, {"test_data/pucch_processor_format2_test_harq129.dat"}, {"test_data/pucch_processor_format2_test_sr129.dat"}, {"test_data/pucch_processor_format2_test_csi1129.dat"}, {"test_data/pucch_processor_format2_test_csi2129.dat"}}, + {{141, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 130, 11, 118, {}, 1, 12, 2, 20935, 822, 6061, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols130.dat"}, {"test_data/pucch_processor_format2_test_harq130.dat"}, {"test_data/pucch_processor_format2_test_sr130.dat"}, {"test_data/pucch_processor_format2_test_csi1130.dat"}, {"test_data/pucch_processor_format2_test_csi2130.dat"}}, + {{205, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 107, 98, 97, {}, 1, 12, 2, 61013, 211, 52092, 7, 1, 0, 0}}, {"test_data/pucch_processor_format2_test_input_symbols131.dat"}, {"test_data/pucch_processor_format2_test_harq131.dat"}, {"test_data/pucch_processor_format2_test_sr131.dat"}, {"test_data/pucch_processor_format2_test_csi1131.dat"}, {"test_data/pucch_processor_format2_test_csi2131.dat"}}, + {{265, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 43, 222, 33, {}, 8, 12, 2, 20728, 796, 45939, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols132.dat"}, {"test_data/pucch_processor_format2_test_harq132.dat"}, {"test_data/pucch_processor_format2_test_sr132.dat"}, {"test_data/pucch_processor_format2_test_csi1132.dat"}, {"test_data/pucch_processor_format2_test_csi2132.dat"}}, + {{239, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 54, 185, 36, {}, 4, 12, 2, 47980, 326, 38475, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols133.dat"}, {"test_data/pucch_processor_format2_test_harq133.dat"}, {"test_data/pucch_processor_format2_test_sr133.dat"}, {"test_data/pucch_processor_format2_test_csi1133.dat"}, {"test_data/pucch_processor_format2_test_csi2133.dat"}}, + {{255, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 3, 252, 0, {}, 3, 12, 2, 14706, 59, 2373, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols134.dat"}, {"test_data/pucch_processor_format2_test_harq134.dat"}, {"test_data/pucch_processor_format2_test_sr134.dat"}, {"test_data/pucch_processor_format2_test_csi1134.dat"}, {"test_data/pucch_processor_format2_test_csi2134.dat"}}, + {{107, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 103, 4, 70, {}, 2, 12, 2, 6111, 68, 18157, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols135.dat"}, {"test_data/pucch_processor_format2_test_harq135.dat"}, {"test_data/pucch_processor_format2_test_sr135.dat"}, {"test_data/pucch_processor_format2_test_csi1135.dat"}, {"test_data/pucch_processor_format2_test_csi2135.dat"}}, + {{259, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 14, 245, 11, {}, 2, 12, 2, 27423, 320, 26880, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols136.dat"}, {"test_data/pucch_processor_format2_test_harq136.dat"}, {"test_data/pucch_processor_format2_test_sr136.dat"}, {"test_data/pucch_processor_format2_test_csi1136.dat"}, {"test_data/pucch_processor_format2_test_csi2136.dat"}}, + {{206, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 25, 181, 17, {}, 1, 12, 2, 62810, 396, 15539, 7, 1, 4, 0}}, {"test_data/pucch_processor_format2_test_input_symbols137.dat"}, {"test_data/pucch_processor_format2_test_harq137.dat"}, {"test_data/pucch_processor_format2_test_sr137.dat"}, {"test_data/pucch_processor_format2_test_csi1137.dat"}, {"test_data/pucch_processor_format2_test_csi2137.dat"}}, + {{267, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 165, 102, 135, {}, 8, 12, 2, 31529, 397, 47637, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols138.dat"}, {"test_data/pucch_processor_format2_test_harq138.dat"}, {"test_data/pucch_processor_format2_test_sr138.dat"}, {"test_data/pucch_processor_format2_test_csi1138.dat"}, {"test_data/pucch_processor_format2_test_csi2138.dat"}}, + {{222, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 181, 41, 112, {}, 5, 12, 2, 58153, 325, 61070, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols139.dat"}, {"test_data/pucch_processor_format2_test_harq139.dat"}, {"test_data/pucch_processor_format2_test_sr139.dat"}, {"test_data/pucch_processor_format2_test_csi1139.dat"}, {"test_data/pucch_processor_format2_test_csi2139.dat"}}, + {{272, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 26, 246, 11, {}, 3, 12, 2, 54039, 137, 57882, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols140.dat"}, {"test_data/pucch_processor_format2_test_harq140.dat"}, {"test_data/pucch_processor_format2_test_sr140.dat"}, {"test_data/pucch_processor_format2_test_csi1140.dat"}, {"test_data/pucch_processor_format2_test_csi2140.dat"}}, + {{79, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 11, 68, 1, {}, 2, 12, 2, 62253, 964, 37, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols141.dat"}, {"test_data/pucch_processor_format2_test_harq141.dat"}, {"test_data/pucch_processor_format2_test_sr141.dat"}, {"test_data/pucch_processor_format2_test_csi1141.dat"}, {"test_data/pucch_processor_format2_test_csi2141.dat"}}, + {{237, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 86, 151, 41, {}, 2, 12, 2, 36187, 961, 44233, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols142.dat"}, {"test_data/pucch_processor_format2_test_harq142.dat"}, {"test_data/pucch_processor_format2_test_sr142.dat"}, {"test_data/pucch_processor_format2_test_csi1142.dat"}, {"test_data/pucch_processor_format2_test_csi2142.dat"}}, + {{114, 14, {nullopt, {0, 0}, cyclic_prefix::NORMAL, {0,1,2,3,}, 55, 59, 50, {}, 2, 12, 2, 64824, 401, 19353, 7, 1, 6, 0}}, {"test_data/pucch_processor_format2_test_input_symbols143.dat"}, {"test_data/pucch_processor_format2_test_harq143.dat"}, {"test_data/pucch_processor_format2_test_sr143.dat"}, {"test_data/pucch_processor_format2_test_csi1143.dat"}, {"test_data/pucch_processor_format2_test_csi2143.dat"}}, // clang-format on }; diff --git a/tests/unittests/phy/upper/channel_processors/pucch_processor_format2_vectortest.cpp b/tests/unittests/phy/upper/channel_processors/pucch_processor_format2_vectortest.cpp index f25195b6a0..640b1c924b 100644 --- a/tests/unittests/phy/upper/channel_processors/pucch_processor_format2_vectortest.cpp +++ b/tests/unittests/phy/upper/channel_processors/pucch_processor_format2_vectortest.cpp @@ -122,7 +122,7 @@ class PucchProcessorF2Fixture : public ::testing::TestWithParamprocess(grid, config); // Assert expected UCI payload. + ASSERT_EQ(result.message.get_status(), uci_status::valid); ASSERT_EQ(result.message.get_harq_ack_bits(), span(expected_harq_ack)); ASSERT_EQ(result.message.get_sr_bits(), span(expected_sr)); ASSERT_EQ(result.message.get_csi_part1_bits(), span(expected_csi_part_1)); diff --git a/tests/unittests/phy/upper/channel_processors/pusch/pusch_decoder_test.cpp b/tests/unittests/phy/upper/channel_processors/pusch/pusch_decoder_test.cpp index e482832183..792a9a350d 100644 --- a/tests/unittests/phy/upper/channel_processors/pusch/pusch_decoder_test.cpp +++ b/tests/unittests/phy/upper/channel_processors/pusch/pusch_decoder_test.cpp @@ -132,10 +132,6 @@ int main(int argc, char** argv) std::unique_ptr pool = create_rx_softbuffer_pool(pool_config); TESTASSERT(pool); - // Reserve softbuffer. - unique_rx_softbuffer softbuffer = pool->reserve_softbuffer({}, {}, nof_codeblocks); - TESTASSERT(softbuffer.is_valid()); - pusch_decoder::configuration dec_cfg = {}; std::size_t cw_offset = 0; @@ -153,9 +149,17 @@ int main(int argc, char** argv) dec_cfg.Nref = cfg.Nref; dec_cfg.nof_layers = cfg.nof_layers; + // Reserve softbuffer. + unique_rx_softbuffer softbuffer = pool->reserve_softbuffer({}, {}, nof_codeblocks); + TESTASSERT(softbuffer.is_valid()); + + // Reset code blocks CRCs. + softbuffer.get().reset_codeblocks_crc(); + // Setup decoder for new data. pusch_decoder_notifier_spy decoder_notifier_spy; - pusch_decoder_buffer& decoder_buffer = decoder->new_data(rx_tb, softbuffer.get(), decoder_notifier_spy, dec_cfg); + pusch_decoder_buffer& decoder_buffer = + decoder->new_data(rx_tb, std::move(softbuffer), decoder_notifier_spy, dec_cfg); // Feed codeword. decoder_buffer.on_new_softbits(span(llrs_all).subspan(cw_offset, cws)); @@ -168,12 +172,13 @@ int main(int argc, char** argv) cw_offset += cws; dec_cfg.new_data = false; - TESTASSERT(dec_stats.tb_crc_ok, "TB CRC checksum failed."); + TESTASSERT(dec_stats.tb_crc_ok, "TB CRC checksum failed for rv={}.", rv); TESTASSERT_EQ(span(rx_tb), span(ref_tb), "TB not decoded correctly."); TESTASSERT_EQ(dec_stats.ldpc_decoder_stats.get_nof_observations(), dec_stats.nof_codeblocks_total, - "Error reporting decoded codeblocks (use_early_stop={}).", - use_early_stop); + "Error reporting decoded codeblocks (use_early_stop={} rv={}).", + use_early_stop, + rv); if (use_early_stop) { TESTASSERT(dec_stats.ldpc_decoder_stats.get_max() <= 2, "Too many decoder iterations."); } else { @@ -181,9 +186,6 @@ int main(int argc, char** argv) dec_stats.ldpc_decoder_stats.get_min(), "Something wrong with iteration counting (no early stop)."); } - - // Force all CRCs to false to test LLR combining. - softbuffer.get().reset_codeblocks_crc(); } } } diff --git a/tests/unittests/phy/upper/channel_processors/pusch/pusch_decoder_test_doubles.h b/tests/unittests/phy/upper/channel_processors/pusch/pusch_decoder_test_doubles.h index 732e30717f..0ed8c93691 100644 --- a/tests/unittests/phy/upper/channel_processors/pusch/pusch_decoder_test_doubles.h +++ b/tests/unittests/phy/upper/channel_processors/pusch/pusch_decoder_test_doubles.h @@ -27,6 +27,7 @@ #include "srsran/phy/upper/channel_processors/channel_processor_factories.h" #include "srsran/phy/upper/channel_processors/pusch/pusch_decoder_buffer.h" #include "srsran/phy/upper/channel_processors/pusch/pusch_decoder_notifier.h" +#include "srsran/phy/upper/channel_processors/pusch/pusch_decoder_result.h" namespace srsran { @@ -36,16 +37,18 @@ class pusch_decoder_spy : public pusch_decoder struct entry_t { span transport_block; pusch_decoder_result stats; - rx_softbuffer* soft_codeword; + unique_rx_softbuffer softbuffer; pusch_decoder_buffer_spy input; configuration config; pusch_decoder_notifier* notifier; }; + ~pusch_decoder_spy() { srsran_assert(entries.empty(), "Entries must be cleared."); } + void set_codeword_length(unsigned codeword_length_) { codeword_length = codeword_length_; } pusch_decoder_buffer& new_data(span transport_block, - rx_softbuffer& softbuffer, + unique_rx_softbuffer softbuffer, pusch_decoder_notifier& notifier, const configuration& cfg) override { @@ -53,7 +56,7 @@ class pusch_decoder_spy : public pusch_decoder entry_t& entry = entries.back(); entry.transport_block = transport_block; entry.stats = {}; - entry.soft_codeword = &softbuffer; + entry.softbuffer = std::move(softbuffer); entry.input.resize(codeword_length); entry.notifier = ¬ifier; entry.config = cfg; diff --git a/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_test_doubles.h b/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_test_doubles.h index 94d37965c4..7db68178ac 100644 --- a/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_test_doubles.h +++ b/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_test_doubles.h @@ -30,7 +30,7 @@ class pusch_processor_dummy : public pusch_processor { public: void process(span data, - rx_softbuffer& softbuffer, + unique_rx_softbuffer softbuffer, pusch_processor_result_notifier& notifier, const resource_grid_reader& grid, const pdu_t& pdu) override diff --git a/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_unittest.cpp b/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_unittest.cpp index a2b934ca81..48d098de61 100644 --- a/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_unittest.cpp +++ b/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_unittest.cpp @@ -136,11 +136,13 @@ class PuschProcessorFixture : public ::testing::TestWithParamcreate_validator(); // Select spies. - estimator_spy = estimator_factory_spy->get_entries().front(); - demodulator_spy = demodulator_factory_spy->get_entries().front(); - demux_spy = demux_factory_spy->get_entries().front(); - decoder_spy = decoder_factory_spy->get_entries().front(); - uci_dec_spy = uci_dec_factory_spy->get_entries().front(); + estimator_spy = estimator_factory_spy->get_entries().front(); + demodulator_spy = demodulator_factory_spy->get_entries().front(); + demux_spy = demux_factory_spy->get_entries().front(); + decoder_spy = decoder_factory_spy->get_entries()[0]; + decoder_spy_debug = decoder_factory_spy->get_entries()[1]; + decoder_spy_info = decoder_factory_spy->get_entries()[2]; + uci_dec_spy = uci_dec_factory_spy->get_entries().front(); } void SetUp() override @@ -231,6 +233,8 @@ class PuschProcessorFixture : public ::testing::TestWithParam pusch_proc; @@ -258,11 +262,13 @@ class PuschProcessorFixture : public ::testing::TestWithParam PuschProcessorFixture::pusch_proc = nullptr; std::unique_ptr PuschProcessorFixture::pusch_proc_info = nullptr; @@ -355,14 +361,15 @@ TEST_P(PuschProcessorFixture, PuschProcessorUnittest) ulsch_info.nof_csi_part2_bits.value()); // Create receive softbuffer. - rx_softbuffer_spy softbuffer_spy; + rx_softbuffer_spy softbuffer_spy; + unique_rx_softbuffer softbuffer(softbuffer_spy); // Resource grid spy. resource_grid_reader_spy rg_spy; // Process PDU. pusch_processor_result_notifier_spy result_notifier; - pusch_proc->process(transport_block, softbuffer_spy, result_notifier, rg_spy, pdu); + pusch_proc->process(transport_block, std::move(softbuffer), result_notifier, rg_spy, pdu); // Extract results. const auto& sch_entries = result_notifier.get_sch_entries(); @@ -431,7 +438,7 @@ TEST_P(PuschProcessorFixture, PuschProcessorUnittest) const pusch_decoder_spy::entry_t& decoder_entry = decoder_spy->get_entries().front(); ASSERT_EQ(decoder_entry.transport_block.data(), transport_block.data()); ASSERT_EQ(decoder_entry.transport_block.size(), transport_block.size()); - ASSERT_EQ(&softbuffer_spy, decoder_entry.soft_codeword); + ASSERT_EQ(&softbuffer_spy, &decoder_entry.softbuffer.get()); ASSERT_EQ(span(demux_entry.sch_data), decoder_entry.input.get_data()); ASSERT_EQ(pdu.codeword.value().ldpc_base_graph, decoder_entry.config.base_graph); ASSERT_EQ(pdu.mcs_descr.modulation, decoder_entry.config.mod); @@ -445,6 +452,9 @@ TEST_P(PuschProcessorFixture, PuschProcessorUnittest) const auto& sch_entry = sch_entries.back(); ASSERT_EQ(decoder_entry.stats.tb_crc_ok, sch_entry.data.tb_crc_ok); ASSERT_EQ(decoder_entry.stats.nof_codeblocks_total, sch_entry.data.nof_codeblocks_total); + + // Clear decoder spy entries - It makes sure the soft buffer is unlocked before destroying the pool. + decoder_spy->clear(); } else { ASSERT_EQ(0, decoder_spy->get_entries().size()); ASSERT_TRUE(sch_entries.empty()); @@ -479,14 +489,18 @@ TEST_P(PuschProcessorFixture, HealthTestFormatterInfo) std::generate(transport_block.begin(), transport_block.end(), [&]() { return static_cast(rgen()); }); // Create receive softbuffer. - rx_softbuffer_spy softbuffer_spy; + rx_softbuffer_spy softbuffer_spy; + unique_rx_softbuffer softbuffer(softbuffer_spy); // Resource grid spy. resource_grid_reader_spy rg_spy; // Process PDU. pusch_processor_result_notifier_spy result_notifier; - pusch_proc_info->process(transport_block, softbuffer_spy, result_notifier, rg_spy, pdu); + pusch_proc_info->process(transport_block, std::move(softbuffer), result_notifier, rg_spy, pdu); + + // Clear decoder spy entries - It makes sure the soft buffer is unlocked before destroying the pool. + decoder_spy_info->clear(); } TEST_P(PuschProcessorFixture, HealthTestFormatterDebug) @@ -496,14 +510,18 @@ TEST_P(PuschProcessorFixture, HealthTestFormatterDebug) std::generate(transport_block.begin(), transport_block.end(), [&]() { return static_cast(rgen()); }); // Create receive softbuffer. - rx_softbuffer_spy softbuffer_spy; + rx_softbuffer_spy softbuffer_spy; + unique_rx_softbuffer softbuffer(softbuffer_spy); // Resource grid spy. resource_grid_reader_spy rg_spy; // Process PDU. pusch_processor_result_notifier_spy result_notifier; - pusch_proc_debug->process(transport_block, softbuffer_spy, result_notifier, rg_spy, pdu); + pusch_proc_debug->process(transport_block, std::move(softbuffer), result_notifier, rg_spy, pdu); + + // Clear decoder spy entries - It makes sure the soft buffer is unlocked before destroying the pool. + decoder_spy_debug->clear(); } // Creates test suite that combines all possible parameters. diff --git a/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_validator_test.cpp b/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_validator_test.cpp index 0fadc05dbb..69d2e77be1 100644 --- a/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_validator_test.cpp +++ b/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_validator_test.cpp @@ -288,12 +288,14 @@ TEST_P(PuschProcessorFixture, PuschProcessorValidatortest) std::vector data; // Prepare softbuffer. - rx_softbuffer_spy softbuffer_spy(ldpc::MAX_CODEBLOCK_SIZE, 0); + rx_softbuffer_spy softbuffer_spy(ldpc::MAX_CODEBLOCK_SIZE, 0); + unique_rx_softbuffer softbuffer(softbuffer_spy); // Process PUSCH PDU. #ifdef ASSERTS_ENABLED pusch_processor_result_notifier_spy result_notifier_spy; - ASSERT_DEATH({ pusch_proc->process(data, softbuffer_spy, result_notifier_spy, grid, param.get_pdu()); }, param.expr); + ASSERT_DEATH({ pusch_proc->process(data, std::move(softbuffer), result_notifier_spy, grid, param.get_pdu()); }, + param.expr); #endif // ASSERTS_ENABLED } diff --git a/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_vectortest.cpp b/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_vectortest.cpp index b5d507d201..9ebe44d39a 100644 --- a/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_vectortest.cpp +++ b/tests/unittests/phy/upper/channel_processors/pusch/pusch_processor_vectortest.cpp @@ -183,16 +183,17 @@ TEST_P(PuschProcessorFixture, PuschProcessorVectortest) std::vector data(expected_data.size()); // Prepare softbuffer. - rx_softbuffer_spy softbuffer_spy(ldpc::MAX_CODEBLOCK_SIZE, + rx_softbuffer_spy softbuffer_spy(ldpc::MAX_CODEBLOCK_SIZE, ldpc::compute_nof_codeblocks(units::bytes(expected_data.size()).to_bits(), config.codeword.value().ldpc_base_graph)); + unique_rx_softbuffer softbuffer(softbuffer_spy); // Make sure the configuration is valid. ASSERT_TRUE(pdu_validator->is_valid(config)); // Process PUSCH PDU. pusch_processor_result_notifier_spy results_notifier; - pusch_proc->process(data, softbuffer_spy, results_notifier, grid, config); + pusch_proc->process(data, std::move(softbuffer), results_notifier, grid, config); // Verify UL-SCH decode results. const auto& sch_entries = results_notifier.get_sch_entries(); diff --git a/tests/unittests/phy/upper/signal_processors/dmrs_pdcch_processor_test.cpp b/tests/unittests/phy/upper/signal_processors/dmrs_pdcch_processor_test.cpp index 4bd0a70e1a..ced7719d9a 100644 --- a/tests/unittests/phy/upper/signal_processors/dmrs_pdcch_processor_test.cpp +++ b/tests/unittests/phy/upper/signal_processors/dmrs_pdcch_processor_test.cpp @@ -42,13 +42,13 @@ int main() for (const test_case_t& test_case : dmrs_pdcch_processor_test_data) { int prb_idx_high = test_case.config.rb_mask.find_highest(); TESTASSERT(prb_idx_high > 1); - unsigned max_prb = static_cast(prb_idx_high + 1); - unsigned max_symb = test_case.config.start_symbol_index + test_case.config.duration; + unsigned max_prb = static_cast(prb_idx_high + 1); + unsigned max_symb = test_case.config.start_symbol_index + test_case.config.duration; + unsigned max_ports = test_case.config.precoding.get_nof_ports(); // Prepare resource grid and resource grid mapper spies. - resource_grid_writer_spy grid(MAX_PORTS, max_symb, max_prb); - std::unique_ptr mapper = - create_resource_grid_mapper(MAX_PORTS, max_symb, NRE * max_prb, grid); + resource_grid_writer_spy grid(max_ports, max_symb, max_prb); + std::unique_ptr mapper = create_resource_grid_mapper(max_ports, NRE * max_prb, grid); // Map DMRS-PDCCH using the test case arguments. dmrs_pdcch->map(*mapper, test_case.config); diff --git a/tests/unittests/phy/upper/signal_processors/dmrs_pdsch_processor_test.cpp b/tests/unittests/phy/upper/signal_processors/dmrs_pdsch_processor_test.cpp index 539e389467..d541ea1eb1 100644 --- a/tests/unittests/phy/upper/signal_processors/dmrs_pdsch_processor_test.cpp +++ b/tests/unittests/phy/upper/signal_processors/dmrs_pdsch_processor_test.cpp @@ -43,13 +43,13 @@ int main() for (const test_case_t& test_case : dmrs_pdsch_processor_test_data) { int prb_idx_high = test_case.config.rb_mask.find_highest(); TESTASSERT(prb_idx_high > 1); - unsigned max_prb = static_cast(prb_idx_high + 1); - unsigned max_symb = get_nsymb_per_slot(cyclic_prefix::NORMAL); + unsigned max_prb = static_cast(prb_idx_high + 1); + unsigned max_symb = get_nsymb_per_slot(cyclic_prefix::NORMAL); + unsigned max_ports = test_case.config.precoding.get_nof_ports(); // Prepare resource grid and resource grid mapper spies. - resource_grid_writer_spy grid(MAX_PORTS, max_symb, max_prb); - std::unique_ptr mapper = - create_resource_grid_mapper(MAX_PORTS, max_symb, NRE * max_prb, grid); + resource_grid_writer_spy grid(max_ports, max_symb, max_prb); + std::unique_ptr mapper = create_resource_grid_mapper(max_ports, NRE * max_prb, grid); // Map DMRS-PDSCH using the test case arguments. dmrs_pdsch->map(*mapper, test_case.config); diff --git a/tests/unittests/phy/upper/signal_processors/nzp_csi_rs_generator_test.cpp b/tests/unittests/phy/upper/signal_processors/nzp_csi_rs_generator_test.cpp index 7338deac3d..787dcaa04b 100644 --- a/tests/unittests/phy/upper/signal_processors/nzp_csi_rs_generator_test.cpp +++ b/tests/unittests/phy/upper/signal_processors/nzp_csi_rs_generator_test.cpp @@ -79,12 +79,13 @@ TEST_P(NzpCsiRsGeneratorFixture, Vector) ASSERT_NE(generator, nullptr); ASSERT_NE(validator, nullptr); - unsigned max_symb = MAX_NSYMB_PER_SLOT; - unsigned max_prb = test_case.config.start_rb + test_case.config.nof_rb; + unsigned max_symb = MAX_NSYMB_PER_SLOT; + unsigned max_prb = test_case.config.start_rb + test_case.config.nof_rb; + unsigned max_ports = test_case.config.precoding.get_nof_ports(); // Prepare resource grid and resource grid mapper spies. - resource_grid_writer_spy grid(MAX_PORTS, max_symb, max_prb); - std::unique_ptr mapper = create_resource_grid_mapper(MAX_PORTS, max_symb, NRE * max_prb, grid); + resource_grid_writer_spy grid(max_ports, max_symb, max_prb); + std::unique_ptr mapper = create_resource_grid_mapper(max_ports, NRE * max_prb, grid); // The configuration must be valid. ASSERT_TRUE(validator->is_valid(test_case.config)); diff --git a/tests/unittests/rlc/rlc_am_pdu_test.cpp b/tests/unittests/rlc/rlc_am_pdu_test.cpp index 7335d3f1e8..567a0654dc 100644 --- a/tests/unittests/rlc/rlc_am_pdu_test.cpp +++ b/tests/unittests/rlc/rlc_am_pdu_test.cpp @@ -30,7 +30,7 @@ template byte_buffer make_pdu_and_log(const std::array& tv) { byte_buffer pdu; - pdu.append(tv); + TESTASSERT(pdu.append(tv)); // write_pdu_to_pcap(4, tv.data(), tv.size()); TODO return pdu; } @@ -40,9 +40,10 @@ void test_rlc_am_12bit_complete_sdu() { test_delimit_logger delimiter{"AM PDU with 12 Bit SN and SI=full"}; - const int header_len = 2, payload_len = 4; - std::array tv_pdu = {0x80, 0x00, 0x11, 0x22, 0x33, 0x44}; - std::array tv_sdu = {}; + const int header_len = 2; + const int payload_len = 4; + std::array tv_pdu = {0x80, 0x00, 0x11, 0x22, 0x33, 0x44}; + std::array tv_sdu = {}; std::copy(tv_pdu.begin() + header_len, tv_pdu.end(), tv_sdu.begin()); rlc_am_pdu_header hdr = {}; { @@ -54,7 +55,7 @@ void test_rlc_am_12bit_complete_sdu() { // Pack byte_buffer buf = make_pdu_and_log(tv_sdu); - rlc_am_write_data_pdu_header(hdr, buf); + TESTASSERT(rlc_am_write_data_pdu_header(hdr, buf)); TESTASSERT(buf == tv_pdu); } } @@ -63,9 +64,10 @@ void test_rlc_am_12bit_complete_sdu() void test_rlc_am_12bit_first_segment() { test_delimit_logger delimiter("AM PDU with 12 bit and SI=first"); - const int header_len = 2, payload_len = 4; - std::array tv_pdu = {0xd1, 0xff, 0x11, 0x22, 0x33, 0x44}; - std::array tv_sdu = {}; + const int header_len = 2; + const int payload_len = 4; + std::array tv_pdu = {0xd1, 0xff, 0x11, 0x22, 0x33, 0x44}; + std::array tv_sdu = {}; std::copy(tv_pdu.begin() + header_len, tv_pdu.end(), tv_sdu.begin()); rlc_am_pdu_header hdr = {}; @@ -81,7 +83,7 @@ void test_rlc_am_12bit_first_segment() { // Pack byte_buffer buf = make_pdu_and_log(tv_sdu); - rlc_am_write_data_pdu_header(hdr, buf); + TESTASSERT(rlc_am_write_data_pdu_header(hdr, buf)); TESTASSERT(buf == tv_pdu); } } @@ -90,9 +92,10 @@ void test_rlc_am_12bit_first_segment() void test_rlc_am_12bit_middle_segment() { test_delimit_logger delimiter("AM PDU with 12 bit and SI=middle"); - const int header_len = 4, payload_len = 4; - std::array tv_pdu = {0xb4, 0x04, 0x04, 0x04, 0x11, 0x22, 0x33, 0x44}; - std::array tv_sdu = {}; + const int header_len = 4; + const int payload_len = 4; + std::array tv_pdu = {0xb4, 0x04, 0x04, 0x04, 0x11, 0x22, 0x33, 0x44}; + std::array tv_sdu = {}; std::copy(tv_pdu.begin() + header_len, tv_pdu.end(), tv_sdu.begin()); rlc_am_pdu_header hdr = {}; @@ -109,7 +112,7 @@ void test_rlc_am_12bit_middle_segment() { // Pack byte_buffer buf = make_pdu_and_log(tv_sdu); - rlc_am_write_data_pdu_header(hdr, buf); + TESTASSERT(rlc_am_write_data_pdu_header(hdr, buf)); TESTASSERT(buf == tv_pdu); } } @@ -118,9 +121,10 @@ void test_rlc_am_12bit_middle_segment() void test_rlc_am_12bit_last_segment() { test_delimit_logger delimiter("AM PDU with 12 bit and SI=last"); - const int header_len = 4, payload_len = 4; - std::array tv_pdu = {0xa4, 0x04, 0x04, 0x04, 0x11, 0x22, 0x33, 0x44}; - std::array tv_sdu = {}; + const int header_len = 4; + const int payload_len = 4; + std::array tv_pdu = {0xa4, 0x04, 0x04, 0x04, 0x11, 0x22, 0x33, 0x44}; + std::array tv_sdu = {}; std::copy(tv_pdu.begin() + header_len, tv_pdu.end(), tv_sdu.begin()); rlc_am_pdu_header hdr = {}; @@ -136,7 +140,7 @@ void test_rlc_am_12bit_last_segment() { // Pack byte_buffer buf = make_pdu_and_log(tv_sdu); - rlc_am_write_data_pdu_header(hdr, buf); + TESTASSERT(rlc_am_write_data_pdu_header(hdr, buf)); TESTASSERT(buf == tv_pdu); } } @@ -145,9 +149,10 @@ void test_rlc_am_12bit_last_segment() void test_rlc_am_18bit_complete_sdu() { test_delimit_logger delimiter("AM PDU with 18 bit and SI=full"); - const int header_len = 3, payload_len = 4; - std::array tv_pdu = {0xc2, 0x02, 0x02, 0x11, 0x22, 0x33, 0x44}; - std::array tv_sdu = {}; + const int header_len = 3; + const int payload_len = 4; + std::array tv_pdu = {0xc2, 0x02, 0x02, 0x11, 0x22, 0x33, 0x44}; + std::array tv_sdu = {}; std::copy(tv_pdu.begin() + header_len, tv_pdu.end(), tv_sdu.begin()); rlc_am_pdu_header hdr = {}; @@ -162,7 +167,7 @@ void test_rlc_am_18bit_complete_sdu() { // Pack byte_buffer buf = make_pdu_and_log(tv_sdu); - rlc_am_write_data_pdu_header(hdr, buf); + TESTASSERT(rlc_am_write_data_pdu_header(hdr, buf)); TESTASSERT(buf == tv_pdu); } } @@ -171,9 +176,10 @@ void test_rlc_am_18bit_complete_sdu() void test_rlc_am_18bit_first_segment() { test_delimit_logger delimiter("AM PDU with 18 bit and SI=first"); - const int header_len = 3, payload_len = 4; - std::array tv_pdu = {0x92, 0x00, 0xff, 0x11, 0x22, 0x33, 0x44}; - std::array tv_sdu = {}; + const int header_len = 3; + const int payload_len = 4; + std::array tv_pdu = {0x92, 0x00, 0xff, 0x11, 0x22, 0x33, 0x44}; + std::array tv_sdu = {}; std::copy(tv_pdu.begin() + header_len, tv_pdu.end(), tv_sdu.begin()); rlc_am_pdu_header hdr = {}; @@ -189,7 +195,7 @@ void test_rlc_am_18bit_first_segment() { // Pack byte_buffer buf = make_pdu_and_log(tv_sdu); - rlc_am_write_data_pdu_header(hdr, buf); + TESTASSERT(rlc_am_write_data_pdu_header(hdr, buf)); TESTASSERT(buf == tv_pdu); } } @@ -198,9 +204,10 @@ void test_rlc_am_18bit_first_segment() void test_rlc_am_18bit_middle_segment() { test_delimit_logger delimiter("AM PDU with 18 bit and SI=middle"); - const int header_len = 5, payload_len = 4; - std::array tv_pdu = {0xb2, 0x00, 0xff, 0x02, 0x02, 0x11, 0x22, 0x33, 0x44}; - std::array tv_sdu = {}; + const int header_len = 5; + const int payload_len = 4; + std::array tv_pdu = {0xb2, 0x00, 0xff, 0x02, 0x02, 0x11, 0x22, 0x33, 0x44}; + std::array tv_sdu = {}; std::copy(tv_pdu.begin() + header_len, tv_pdu.end(), tv_sdu.begin()); rlc_am_pdu_header hdr = {}; @@ -216,7 +223,7 @@ void test_rlc_am_18bit_middle_segment() { // Pack byte_buffer buf = make_pdu_and_log(tv_sdu); - rlc_am_write_data_pdu_header(hdr, buf); + TESTASSERT(rlc_am_write_data_pdu_header(hdr, buf)); TESTASSERT(buf == tv_pdu); } } @@ -225,9 +232,10 @@ void test_rlc_am_18bit_middle_segment() void test_rlc_am_18bit_last_segment() { test_delimit_logger delimiter("AM PDU with 18 bit and SI=last"); - const int header_len = 5, payload_len = 4; - std::array tv_pdu = {0xa2, 0x00, 0xff, 0x02, 0x02, 0x11, 0x22, 0x33, 0x44}; - std::array tv_sdu = {}; + const int header_len = 5; + const int payload_len = 4; + std::array tv_pdu = {0xa2, 0x00, 0xff, 0x02, 0x02, 0x11, 0x22, 0x33, 0x44}; + std::array tv_sdu = {}; std::copy(tv_pdu.begin() + header_len, tv_pdu.end(), tv_sdu.begin()); rlc_am_pdu_header hdr = {}; @@ -243,7 +251,7 @@ void test_rlc_am_18bit_last_segment() { // Pack byte_buffer buf = make_pdu_and_log(tv_sdu); - rlc_am_write_data_pdu_header(hdr, buf); + TESTASSERT(rlc_am_write_data_pdu_header(hdr, buf)); TESTASSERT(buf == tv_pdu); } } @@ -252,9 +260,10 @@ void test_rlc_am_18bit_last_segment() void test_rlc_am_18bit_malformed() { test_delimit_logger delimiter("Malformed AM PDU with 18 bit"); - const int header_len = 5, payload_len = 4; - std::array tv = {0xb7, 0x00, 0xff, 0x02, 0x02, 0x11, 0x22, 0x33, 0x44}; - byte_buffer pdu = make_pdu_and_log(tv); + const int header_len = 5; + const int payload_len = 4; + std::array tv = {0xb7, 0x00, 0xff, 0x02, 0x02, 0x11, 0x22, 0x33, 0x44}; + byte_buffer pdu = make_pdu_and_log(tv); // unpack PDU rlc_am_pdu_header header = {}; diff --git a/tests/unittests/rlc/rlc_rx_am_test.cpp b/tests/unittests/rlc/rlc_rx_am_test.cpp index 32ee8314e8..0ac05a496e 100644 --- a/tests/unittests/rlc/rlc_rx_am_test.cpp +++ b/tests/unittests/rlc/rlc_rx_am_test.cpp @@ -161,7 +161,7 @@ class rlc_rx_am_test : public ::testing::Test, public ::testing::WithParamInterf } byte_buffer pdu_buf; logger.debug("AMD PDU header: {}", hdr); - rlc_am_write_data_pdu_header(hdr, pdu_buf); + ASSERT_TRUE(rlc_am_write_data_pdu_header(hdr, pdu_buf)); pdu_buf.append(payload); pdu_list.push_back(std::move(pdu_buf)); diff --git a/tests/unittests/rlc/rlc_tx_am_test.cpp b/tests/unittests/rlc/rlc_tx_am_test.cpp index 8168aa1ca9..00c998ea27 100644 --- a/tests/unittests/rlc/rlc_tx_am_test.cpp +++ b/tests/unittests/rlc/rlc_tx_am_test.cpp @@ -99,6 +99,7 @@ class rlc_tx_am_test : public ::testing::Test, public ::testing::WithParamInterf config.max_retx_thresh = 4; config.poll_pdu = 4; config.poll_byte = 25; + config.max_window = 0; // Create test frame tester = std::make_unique(config.sn_field_length); @@ -1454,7 +1455,7 @@ TEST_P(rlc_tx_am_test, expired_poll_retransmit_timer_triggers_retx) // check if the polling (P) bit IS set in the PDU header (because of previously expired poll_retransmit_timer) byte_buffer_chain pdu = rlc->pull_pdu(rlc->get_buffer_state() - 2); rlc_am_pdu_header pdu_hdr = {}; - rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr); + ASSERT_TRUE(rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr)); EXPECT_TRUE(pdu_hdr.p); EXPECT_EQ(tester->bsr, pdu_size); EXPECT_EQ(tester->bsr_count, n_bsr); @@ -1465,7 +1466,7 @@ TEST_P(rlc_tx_am_test, expired_poll_retransmit_timer_triggers_retx) // check if the polling (P) bit is NOT set anymore in the PDU header (non-empty queues and timer not expired again) byte_buffer_chain pdu = rlc->pull_pdu(rlc->get_buffer_state() - 1); rlc_am_pdu_header pdu_hdr = {}; - rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr); + ASSERT_TRUE(rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr)); EXPECT_FALSE(pdu_hdr.p); EXPECT_EQ(tester->bsr, pdu_size); EXPECT_EQ(tester->bsr_count, n_bsr); @@ -1476,7 +1477,7 @@ TEST_P(rlc_tx_am_test, expired_poll_retransmit_timer_triggers_retx) // check if the polling (P) bit IS set anymore in the PDU header (because RLC queues are run empty) byte_buffer_chain pdu = rlc->pull_pdu(rlc->get_buffer_state()); rlc_am_pdu_header pdu_hdr = {}; - rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr); + ASSERT_TRUE(rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr)); EXPECT_TRUE(pdu_hdr.p); EXPECT_EQ(tester->bsr, pdu_size); EXPECT_EQ(tester->bsr_count, n_bsr); @@ -1509,7 +1510,7 @@ TEST_P(rlc_tx_am_test, expired_poll_retransmit_timer_sets_polling_bit) // check if the polling (P) bit is NOT set in the PDU header byte_buffer_chain pdu = rlc->pull_pdu(rlc->get_buffer_state() - 3); rlc_am_pdu_header pdu_hdr = {}; - rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr); + ASSERT_TRUE(rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr)); EXPECT_FALSE(pdu_hdr.p); } @@ -1532,7 +1533,7 @@ TEST_P(rlc_tx_am_test, expired_poll_retransmit_timer_sets_polling_bit) // check if the polling (P) bit IS set in the PDU header (because of previously expired poll_retransmit_timer) byte_buffer_chain pdu = rlc->pull_pdu(rlc->get_buffer_state() - 2); rlc_am_pdu_header pdu_hdr = {}; - rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr); + ASSERT_TRUE(rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr)); EXPECT_TRUE(pdu_hdr.p); } @@ -1541,7 +1542,7 @@ TEST_P(rlc_tx_am_test, expired_poll_retransmit_timer_sets_polling_bit) // check if the polling (P) bit is NOT set anymore in the PDU header (non-empty queues and timer not expired again) byte_buffer_chain pdu = rlc->pull_pdu(rlc->get_buffer_state() - 1); rlc_am_pdu_header pdu_hdr = {}; - rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr); + ASSERT_TRUE(rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr)); EXPECT_FALSE(pdu_hdr.p); } @@ -1550,7 +1551,7 @@ TEST_P(rlc_tx_am_test, expired_poll_retransmit_timer_sets_polling_bit) // check if the polling (P) bit IS set anymore in the PDU header (because RLC queues are run empty) byte_buffer_chain pdu = rlc->pull_pdu(rlc->get_buffer_state()); rlc_am_pdu_header pdu_hdr = {}; - rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr); + ASSERT_TRUE(rlc_am_read_data_pdu_header(byte_buffer(pdu.begin(), pdu.end()), sn_size, &pdu_hdr)); EXPECT_TRUE(pdu_hdr.p); } } diff --git a/tests/unittests/rrc/rrc_ue_capability_transfer_proc_test.cpp b/tests/unittests/rrc/rrc_ue_capability_transfer_proc_test.cpp index 0439bbac57..745658f9ec 100644 --- a/tests/unittests/rrc/rrc_ue_capability_transfer_proc_test.cpp +++ b/tests/unittests/rrc/rrc_ue_capability_transfer_proc_test.cpp @@ -24,7 +24,7 @@ #include "rrc_ue_test_messages.h" #include "srsran/adt/byte_buffer.h" #include "srsran/rrc/rrc_du_factory.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/test_utils.h" #include diff --git a/tests/unittests/rrc/rrc_ue_dl_info_transfer_proc_test.cpp b/tests/unittests/rrc/rrc_ue_dl_info_transfer_proc_test.cpp index e615a4b2ed..07e7764bf0 100644 --- a/tests/unittests/rrc/rrc_ue_dl_info_transfer_proc_test.cpp +++ b/tests/unittests/rrc/rrc_ue_dl_info_transfer_proc_test.cpp @@ -23,7 +23,7 @@ #include "rrc_ue_test_helpers.h" #include "rrc_ue_test_messages.h" #include "srsran/adt/byte_buffer.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/test_utils.h" #include diff --git a/tests/unittests/rrc/rrc_ue_reconfig_proc_test.cpp b/tests/unittests/rrc/rrc_ue_reconfig_proc_test.cpp index 484b4f974c..fe7d95ed10 100644 --- a/tests/unittests/rrc/rrc_ue_reconfig_proc_test.cpp +++ b/tests/unittests/rrc/rrc_ue_reconfig_proc_test.cpp @@ -23,7 +23,7 @@ #include "rrc_ue_test_helpers.h" #include "rrc_ue_test_messages.h" #include "srsran/adt/byte_buffer.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/test_utils.h" #include diff --git a/tests/unittests/rrc/rrc_ue_reest_proc_test.cpp b/tests/unittests/rrc/rrc_ue_reest_proc_test.cpp index bd07b9f558..549bbf148f 100644 --- a/tests/unittests/rrc/rrc_ue_reest_proc_test.cpp +++ b/tests/unittests/rrc/rrc_ue_reest_proc_test.cpp @@ -76,26 +76,6 @@ TEST_F(rrc_ue_reest, when_valid_reestablishment_request_received_but_security_co check_initial_ue_message_sent(); } -/// Test the RRC Reestablishment -TEST_F(rrc_ue_reest, - when_valid_reestablishment_request_for_different_du_received_then_rrc_reestablishment_without_old_ue_index_sent) -{ - connect_amf(); - ue_index_t old_ue_index = generate_ue_index(uint_to_du_index(1), 2); - add_ue_reestablishment_context(old_ue_index); - receive_valid_reestablishment_request(1, to_rnti(0x4601)); - - // check if SRB1 was created - check_srb1_exists(); - - // check if the RRC Reestablishment was generated - ASSERT_EQ(get_last_srb(), srb_id_t::srb1); - // check if the old ue index was send to the f1ap - ASSERT_EQ(get_old_ue_index(), ue_index_t::invalid); - - receive_reestablishment_complete(); -} - /// Test the RRC Reestablishment TEST_F(rrc_ue_reest, when_valid_reestablishment_request_for_same_du_received_then_rrc_reestablishment_with_old_ue_index_sent) @@ -110,8 +90,6 @@ TEST_F(rrc_ue_reest, // check if the RRC message was sent over SRB1 ASSERT_EQ(get_last_srb(), srb_id_t::srb1); - // check if the old ue index was send to the f1ap - ASSERT_EQ(get_old_ue_index(), old_ue_index); receive_reestablishment_complete(); } diff --git a/tests/unittests/rrc/rrc_ue_setup_proc_test.cpp b/tests/unittests/rrc/rrc_ue_setup_proc_test.cpp index 63e3d9f99c..53edec1ee2 100644 --- a/tests/unittests/rrc/rrc_ue_setup_proc_test.cpp +++ b/tests/unittests/rrc/rrc_ue_setup_proc_test.cpp @@ -23,7 +23,7 @@ #include "rrc_ue_test_helpers.h" #include "srsran/adt/byte_buffer.h" #include "srsran/rrc/rrc_du_factory.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/test_utils.h" #include diff --git a/tests/unittests/rrc/rrc_ue_smc_proc_test.cpp b/tests/unittests/rrc/rrc_ue_smc_proc_test.cpp index eebfc29bb2..2439e315de 100644 --- a/tests/unittests/rrc/rrc_ue_smc_proc_test.cpp +++ b/tests/unittests/rrc/rrc_ue_smc_proc_test.cpp @@ -24,7 +24,7 @@ #include "rrc_ue_test_messages.h" #include "srsran/adt/byte_buffer.h" #include "srsran/rrc/rrc_du_factory.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/test_utils.h" #include @@ -130,11 +130,8 @@ TEST_F(rrc_ue_smc, when_reply_missing_procedure_timeout) // check that UE has been created and was not requested to be released check_ue_release_not_requested(); - // tick timer until RRC setup complete timer fires + // tick timer until RRC procedure timer fires tick_timer(); - // verify that RRC requested UE context release - check_ue_release_requested(); - ASSERT_TRUE(t.ready()); } diff --git a/tests/unittests/rrc/rrc_ue_test_helpers.h b/tests/unittests/rrc/rrc_ue_test_helpers.h index 712565cf19..e1c6ab7286 100644 --- a/tests/unittests/rrc/rrc_ue_test_helpers.h +++ b/tests/unittests/rrc/rrc_ue_test_helpers.h @@ -116,12 +116,6 @@ class rrc_ue_test_helper return {rrc_ue_f1ap_notifier.last_rrc_pdu.begin(), rrc_ue_f1ap_notifier.last_rrc_pdu.end()}; } - ue_index_t get_old_ue_index() - { - EXPECT_EQ(rrc_ue_f1ap_notifier.last_srb_id, srb_id_t::srb1); - return rrc_ue_f1ap_notifier.last_ue_index; - } - byte_buffer get_srb2_pdu() { // generated PDU must not be empty @@ -204,11 +198,10 @@ class rrc_ue_test_helper rrc_ue->get_ul_dcch_pdu_handler().handle_ul_dcch_pdu(srb_id_t::srb1, byte_buffer{rrc_setup_complete_pdu}); } - void send_dl_info_transfer(byte_buffer nas_msg) + void send_dl_info_transfer(byte_buffer nas_pdu) { - dl_nas_transport_message msg{std::move(nas_msg)}; // inject RRC setup complete - rrc_ue->handle_dl_nas_transport_message(msg); + rrc_ue->handle_dl_nas_transport_message(std::move(nas_pdu)); } void check_srb1_exists() @@ -233,12 +226,12 @@ class rrc_ue_test_helper void check_ue_release_not_requested() { - ASSERT_NE(rrc_ue_ev_notifier.last_rrc_ue_context_release_command.ue_index, ALLOCATED_UE_INDEX); + ASSERT_NE(rrc_ue_ev_notifier.last_cu_cp_ue_context_release_command.ue_index, ALLOCATED_UE_INDEX); } void check_ue_release_requested() { - ASSERT_EQ(rrc_ue_ev_notifier.last_rrc_ue_context_release_command.ue_index, ALLOCATED_UE_INDEX); + ASSERT_EQ(rrc_ue_ev_notifier.last_cu_cp_ue_context_release_command.ue_index, ALLOCATED_UE_INDEX); } void receive_smc_complete() diff --git a/tests/unittests/rrc/test_helpers.h b/tests/unittests/rrc/test_helpers.h index 4a74ebe446..60c32225e3 100644 --- a/tests/unittests/rrc/test_helpers.h +++ b/tests/unittests/rrc/test_helpers.h @@ -24,7 +24,7 @@ #include "srsran/cu_cp/cell_meas_manager.h" #include "srsran/cu_cp/cu_cp_types.h" -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" namespace srsran { namespace srs_cu_cp { @@ -34,28 +34,26 @@ class dummy_rrc_f1ap_pdu_notifier : public rrc_pdu_f1ap_notifier public: dummy_rrc_f1ap_pdu_notifier() = default; - void on_new_rrc_pdu(const srb_id_t srb_id, const byte_buffer& pdu, ue_index_t old_ue_index) override + void on_new_rrc_pdu(const srb_id_t srb_id, const byte_buffer& pdu) override { - last_rrc_pdu = pdu.copy(); - last_srb_id = srb_id; - last_ue_index = old_ue_index; + last_rrc_pdu = pdu.copy(); + last_srb_id = srb_id; } byte_buffer last_rrc_pdu; srb_id_t last_srb_id; - ue_index_t last_ue_index; }; class dummy_rrc_ue_du_processor_adapter : public rrc_ue_du_processor_notifier { public: async_task - on_ue_context_release_command(const rrc_ue_context_release_command& msg) override + on_ue_context_release_command(const cu_cp_ue_context_release_command& msg) override { logger.info("Received UE Context Release Command"); - last_rrc_ue_context_release_command.ue_index = msg.ue_index; - last_rrc_ue_context_release_command.cause = msg.cause; - last_rrc_ue_context_release_command.rrc_release_pdu = msg.rrc_release_pdu.copy(); + last_cu_cp_ue_context_release_command.ue_index = msg.ue_index; + last_cu_cp_ue_context_release_command.cause = msg.cause; + last_cu_cp_ue_context_release_command.rrc_release_pdu = msg.rrc_release_pdu.copy(); return launch_async([](coro_context>& ctx) mutable { CORO_BEGIN(ctx); @@ -73,8 +71,8 @@ class dummy_rrc_ue_du_processor_adapter : public rrc_ue_du_processor_notifier }); } - srb_creation_message last_srb_creation_message; - rrc_ue_context_release_command last_rrc_ue_context_release_command; + srb_creation_message last_srb_creation_message; + cu_cp_ue_context_release_command last_cu_cp_ue_context_release_command; private: srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST"); @@ -83,13 +81,13 @@ class dummy_rrc_ue_du_processor_adapter : public rrc_ue_du_processor_notifier class dummy_rrc_ue_ngap_adapter : public rrc_ue_nas_notifier, public rrc_ue_control_notifier { public: - void on_initial_ue_message(const initial_ue_message& msg) override + void on_initial_ue_message(const cu_cp_initial_ue_message& msg) override { logger.info("Received Initial UE Message"); initial_ue_msg_received = true; } - void on_ul_nas_transport_message(const ul_nas_transport_message& msg) override + void on_ul_nas_transport_message(const cu_cp_ul_nas_transport& msg) override { logger.info("Received UL NAS Transport message"); } @@ -103,7 +101,7 @@ class dummy_rrc_ue_ngap_adapter : public rrc_ue_nas_notifier, public rrc_ue_cont const nr_cell_global_id_t& cgi, const unsigned tac) override { - logger.info("Received inter CU handover related RRC Reconfiguration Complete."); + logger.info("ue={}: Received inter CU handover related RRC Reconfiguration Complete", ue_index); } bool initial_ue_msg_received = false; @@ -120,19 +118,22 @@ class dummy_rrc_ue_cu_cp_adapter : public rrc_ue_reestablishment_notifier rrc_reestablishment_ue_context_t on_rrc_reestablishment_request(pci_t old_pci, rnti_t old_c_rnti, ue_index_t ue_index) override { - logger.info("Received RRC Reestablishment Request from ue={} with old_pci={} and old_c_rnti={}", - ue_index, - old_pci, - old_c_rnti); + logger.info("ue={} old_pci={} old_c_rnti={}: Received RRC Reestablishment Request", ue_index, old_pci, old_c_rnti); return reest_context; } - void on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) override + async_task on_ue_transfer_required(ue_index_t ue_index, ue_index_t old_ue_index) override { logger.info("Requested a UE context transfer from ue={} with old_ue={}.", ue_index, old_ue_index); + return launch_async([](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + CORO_RETURN(true); + }); } + void on_ue_removal_required(ue_index_t ue_index) override { logger.info("ue={}: Requested a UE removal", ue_index); } + private: rrc_reestablishment_ue_context_t reest_context = {}; srslog::basic_logger& logger = srslog::fetch_basic_logger("TEST"); @@ -169,9 +170,9 @@ struct dummy_ue_task_scheduler : public rrc_ue_task_scheduler { void tick_timer() { timer_db.tick(); } private: - async_task_sequencer ctrl_loop{16}; - timer_manager& timer_db; - task_executor& exec; + fifo_async_task_scheduler ctrl_loop{16}; + timer_manager& timer_db; + task_executor& exec; }; } // namespace srs_cu_cp diff --git a/tests/unittests/scheduler/cell_resource_grid/cell_resource_grid_test.cpp b/tests/unittests/scheduler/cell_resource_grid/cell_resource_grid_test.cpp index 69530c455c..00311ed30f 100644 --- a/tests/unittests/scheduler/cell_resource_grid/cell_resource_grid_test.cpp +++ b/tests/unittests/scheduler/cell_resource_grid/cell_resource_grid_test.cpp @@ -117,13 +117,13 @@ TEST(cell_resource_grid_test, test_all) bwp_sch_grant_info grant{bwp_cfg, {2, 14}, {5, 10}}; cell_grid.fill(grant); - TESTASSERT(cell_grid.collides(subcarrier_spacing::kHz15, {0, 14}, {0, 52})); - TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz15, {0, 14}, {0, 5})); - TESTASSERT(cell_grid.collides(subcarrier_spacing::kHz15, {2, 3}, {0, 6})); - TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz15, {0, 2}, {0, 6})); + TESTASSERT(cell_grid.collides(subcarrier_spacing::kHz15, {0, 14}, crb_interval{0, 52})); + TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz15, {0, 14}, crb_interval{0, 5})); + TESTASSERT(cell_grid.collides(subcarrier_spacing::kHz15, {2, 3}, crb_interval{0, 6})); + TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz15, {0, 2}, crb_interval{0, 6})); cell_grid.clear(); - TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz15, {2, 3}, {0, 6})); + TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz15, {2, 3}, crb_interval{0, 6})); } // Narrow BWP, 15 kHz case. @@ -133,7 +133,7 @@ TEST(cell_resource_grid_test, test_all) bwp_cfg.scs = srsran::subcarrier_spacing::kHz15; bwp_cfg.crbs = {10, 30}; - TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz15, {0, 14}, {0, 52})); + TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz15, {0, 14}, crb_interval{0, 52})); bwp_sch_grant_info grant{bwp_cfg, {2, 14}, {5, 10}}; cell_grid.fill(grant); @@ -153,7 +153,7 @@ TEST(cell_resource_grid_test, test_all) bwp_cfg.scs = srsran::subcarrier_spacing::kHz120; bwp_cfg.crbs = {10, 275}; - TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz120, {0, 14}, {0, 265})); + TESTASSERT(not cell_grid.collides(subcarrier_spacing::kHz120, {0, 14}, crb_interval{0, 265})); bwp_sch_grant_info grant{bwp_cfg, {2, 14}, {5, 200}}; cell_grid.fill(grant); diff --git a/tests/unittests/scheduler/pdcch/pdcch_resource_allocator_test.cpp b/tests/unittests/scheduler/pdcch/pdcch_resource_allocator_test.cpp index 10046b8868..7cefe002c9 100644 --- a/tests/unittests/scheduler/pdcch/pdcch_resource_allocator_test.cpp +++ b/tests/unittests/scheduler/pdcch/pdcch_resource_allocator_test.cpp @@ -21,8 +21,8 @@ */ #include "../test_utils/config_generators.h" -#include "lib/scheduler/pdcch_scheduling/pdcch_config_helpers.h" #include "lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.h" +#include "lib/scheduler/support/pdcch/pdcch_mapping.h" #include "srsran/ran/pdcch/pdcch_candidates.h" #include "srsran/support/test_utils.h" #include diff --git a/tests/unittests/scheduler/policy/scheduler_policy_test.cpp b/tests/unittests/scheduler/policy/scheduler_policy_test.cpp index 425af57c89..e44e90c899 100644 --- a/tests/unittests/scheduler/policy/scheduler_policy_test.cpp +++ b/tests/unittests/scheduler/policy/scheduler_policy_test.cpp @@ -41,7 +41,7 @@ class dummy_pdsch_allocator : public ue_pdsch_allocator dummy_pdsch_allocator(cell_resource_allocator& res_grid_) : res_grid(res_grid_) {} - bool allocate_dl_grant(const ue_pdsch_grant& grant) override + alloc_outcome allocate_dl_grant(const ue_pdsch_grant& grant) override { last_grants.push_back(grant); const auto& cell_cfg_cmn = grant.user->get_pcell().cfg().cell_cfg_common; @@ -49,7 +49,7 @@ class dummy_pdsch_allocator : public ue_pdsch_allocator cell_cfg_cmn.dl_cfg_common.init_dl_bwp.generic_params.scs, cell_cfg_cmn.dl_cfg_common.init_dl_bwp.pdsch_common.pdsch_td_alloc_list[grant.time_res_index].symbols, grant.crbs}); - return true; + return alloc_outcome::success; } }; @@ -61,14 +61,14 @@ class dummy_pusch_allocator : public ue_pusch_allocator dummy_pusch_allocator(cell_resource_allocator& res_grid_) : res_grid(res_grid_) {} - bool allocate_ul_grant(const ue_pusch_grant& grant) override + alloc_outcome allocate_ul_grant(const ue_pusch_grant& grant) override { last_grants.push_back(grant); const auto& cell_cfg_cmn = grant.user->get_pcell().cfg().cell_cfg_common; unsigned k2 = cell_cfg_cmn.ul_cfg_common.init_ul_bwp.pusch_cfg_common->pusch_td_alloc_list[grant.time_res_index].k2; res_grid[k2].ul_res_grid.fill( grant_info{cell_cfg_cmn.ul_cfg_common.init_ul_bwp.generic_params.scs, {0, 14}, grant.crbs}); - return true; + return alloc_outcome::success; } }; @@ -106,11 +106,9 @@ class base_scheduler_policy_test unsigned dl_idx = next_slot.to_uint() % 2; for (unsigned i = 0; i != 2; ++i) { if (dl_idx == i) { - sched->dl_sched(pdsch_alloc, ue_res_grid, ues, true); - sched->dl_sched(pdsch_alloc, ue_res_grid, ues, false); + sched->dl_sched(pdsch_alloc, ue_res_grid, ues); } else { - sched->ul_sched(pusch_alloc, ue_res_grid, ues, true); - sched->ul_sched(pusch_alloc, ue_res_grid, ues, false); + sched->ul_sched(pusch_alloc, ue_res_grid, ues); } } @@ -194,9 +192,16 @@ class scheduler_policy_test : public base_scheduler_policy_test, public ::testin TEST_P(scheduler_policy_test, when_coreset0_used_then_dl_grant_is_within_bounds_of_coreset0_rbs) { - auto ue_req = make_ue_create_req(to_du_ue_index(0), to_rnti(0x4601), {uint_to_lcid(4)}, uint_to_lcg_id(0)); - (*ue_req.cfg.cells)[0].serv_cell_cfg.init_dl_bwp.pdcch_cfg->search_spaces.clear(); + auto ue_req = make_ue_create_req(to_du_ue_index(0), to_rnti(0x4601), {uint_to_lcid(4)}, uint_to_lcg_id(0)); + auto& ss_list = (*ue_req.cfg.cells)[0].serv_cell_cfg.init_dl_bwp.pdcch_cfg->search_spaces; + ss_list.clear(); + ss_list.push_back(config_helpers::make_default_common_search_space_config()); + ss_list.back().set_non_ss0_id(to_search_space_id(2)); + // Note: We use Aggregation Level 2 to avoid collisions with CORESET#0 PDCCH candidates. + ss_list.back().set_non_ss0_nof_candidates({0, 2, 0, 0, 0}); ue& u = add_ue(ue_req); + // Note: set CQI=15 to use low aggregation level. + u.get_pcell().handle_csi_report(csi_report_data{nullopt, nullopt, nullopt, nullopt, cqi_value{15U}}); push_dl_bs(u.ue_index, uint_to_lcid(4), 100000000); run_slot(); @@ -251,29 +256,6 @@ TEST_P(scheduler_policy_test, scheduler_favors_ss_with_higher_nof_candidates_for ASSERT_EQ(this->pusch_alloc.last_grants[0].ss_id, to_search_space_id(2)); } -TEST_P(scheduler_policy_test, scheduler_favors_coreset_gt_0_when_ss_has_equal_nof_candidates_for_aggr_lvl) -{ - sched_ue_creation_request_message ue_req = - make_ue_create_req(to_du_ue_index(0), to_rnti(0x4601), {uint_to_lcid(5)}, uint_to_lcg_id(2)); - - const ue& u = add_ue(ue_req); - - push_dl_bs(u.ue_index, uint_to_lcid(5), 1053); - notify_ul_bsr(u.ue_index, uint_to_lcg_id(2), 1053); - - run_slot(); - - ASSERT_FALSE(this->pdsch_alloc.last_grants.empty()); - ASSERT_EQ(this->pdsch_alloc.last_grants[0].user->ue_index, u.ue_index); - // Scheduler choose SS#2 since nof. candidates in all SearchSpaces for Aggr. lvl 4 are equal. - ASSERT_EQ(this->pdsch_alloc.last_grants[0].ss_id, to_search_space_id(2)); - - ASSERT_FALSE(this->pusch_alloc.last_grants.empty()); - ASSERT_EQ(this->pusch_alloc.last_grants[0].user->ue_index, u.ue_index); - // Scheduler choose SS#2 since nof. candidates in all SearchSpaces for Aggr. lvl 4 are equal. - ASSERT_EQ(this->pusch_alloc.last_grants[0].ss_id, to_search_space_id(2)); -} - TEST_P(scheduler_policy_test, scheduler_allocates_more_than_one_ue_in_case_their_bsr_is_low) { lcg_id_t lcg_id = uint_to_lcg_id(2); @@ -306,6 +288,22 @@ TEST_P(scheduler_policy_test, scheduler_allocates_more_than_one_ue_in_case_their ASSERT_FALSE(pdsch_alloc.last_grants[0].crbs.overlaps(pdsch_alloc.last_grants[1].crbs)); } +TEST_P(scheduler_policy_test, scheduler_allocates_ues_with_sr_opportunity_first_than_ues_with_only_ul_data) +{ + lcg_id_t lcg_id = uint_to_lcg_id(2); + const ue& u1 = add_ue(make_ue_create_req(to_du_ue_index(0), to_rnti(0x4601), {uint_to_lcid(5)}, lcg_id)); + ue& u2 = add_ue(make_ue_create_req(to_du_ue_index(1), to_rnti(0x4602), {uint_to_lcid(5)}, lcg_id)); + + notify_ul_bsr(u1.ue_index, lcg_id, 200); + u2.handle_sr_indication(); + + run_slot(); + + ASSERT_GE(pusch_alloc.last_grants.size(), 1); + ASSERT_EQ(pusch_alloc.last_grants.front().user->ue_index, to_du_ue_index(1)) + << fmt::format("UE with SR opportunity should have been scheduled first."); +} + class scheduler_policy_partial_slot_tdd_test : public base_scheduler_policy_test, public ::testing::TestWithParam { @@ -403,6 +401,36 @@ TEST_F(scheduler_round_robin_test, round_robin_does_not_account_ues_with_empty_b } } +TEST_F(scheduler_round_robin_test, round_robin_must_not_attempt_to_allocate_twice_for_same_ue_in_one_slot) +{ + const lcg_id_t lcg_id = uint_to_lcg_id(2); + ue& u1 = add_ue(make_ue_create_req(to_du_ue_index(0), to_rnti(0x4601), {uint_to_lcid(5)}, lcg_id)); + + auto get_pdsch_grant_count_for_ue = [&](const rnti_t crnti) { + return std::count_if(pdsch_alloc.last_grants.begin(), + pdsch_alloc.last_grants.end(), + [&](const ue_pdsch_grant& grant) { return crnti == grant.user->crnti; }); + }; + auto get_pusch_grant_count_for_ue = [&](const rnti_t crnti) { + return std::count_if(pusch_alloc.last_grants.begin(), + pusch_alloc.last_grants.end(), + [&](const ue_pusch_grant& grant) { return crnti == grant.user->crnti; }); + }; + + // Action: Push buffer status notification for DL + Ul and a SR indication. + push_dl_bs(u1.ue_index, uint_to_lcid(5), 20000000); + notify_ul_bsr(u1.ue_index, lcg_id, 2000000); + u1.handle_sr_indication(); + + // Action: Run for at least 256 slots or more so that there are some HARQs with pending reTx. + // Status: Policy scheduler should not allocate reTx and new Tx for the same UE at the same time. + for (unsigned i = 0; i != 512; ++i) { + ASSERT_LE(get_pdsch_grant_count_for_ue(u1.crnti), 1); + ASSERT_LE(get_pusch_grant_count_for_ue(u1.crnti), 1); + run_slot(); + } +} + INSTANTIATE_TEST_SUITE_P(scheduler_policy, scheduler_policy_test, testing::Values(policy_type::time_rr)); INSTANTIATE_TEST_SUITE_P(scheduler_policy, scheduler_policy_partial_slot_tdd_test, diff --git a/tests/unittests/scheduler/scheduler_ue_removal_test.cpp b/tests/unittests/scheduler/scheduler_ue_removal_test.cpp index c33e8cd5e3..3de8bebf32 100644 --- a/tests/unittests/scheduler/scheduler_ue_removal_test.cpp +++ b/tests/unittests/scheduler/scheduler_ue_removal_test.cpp @@ -124,11 +124,17 @@ TEST_F(sched_ue_removal_test, when_ue_has_pending_harqs_then_scheduler_waits_for ASSERT_NE(pucch, nullptr); ASSERT_FALSE(notif.last_ue_index_deleted.has_value()); - // HARQ-ACK should empty the HARQ process. + // HARQ-ACK(s) should empty the HARQ process. uci_indication uci; uci.cell_index = to_du_cell_index(0); uci.slot_rx = last_result_slot(); - uci.ucis.push_back(create_uci_pdu_with_harq_ack(ue_index, *pucch)); + // Note: There can be more than one PUCCH for the same UE. We need to ACK all of them, otherwise the HARQ is not + // emptied. + for (const auto& pucch_alloc : last_sched_res->ul.pucchs) { + if (pucch_alloc.crnti == rnti) { + uci.ucis.push_back(create_uci_pdu_with_harq_ack(ue_index, pucch_alloc)); + } + } this->sched->handle_uci_indication(uci); // The UE should be removed at this point. @@ -175,3 +181,55 @@ TEST_F(sched_ue_removal_test, when_ue_is_removed_then_any_pending_uci_does_not_c // No log warnings should be generated. ASSERT_EQ(initial_nof_warnings, test_spy.get_warning_counter() + test_spy.get_error_counter()); } + +TEST_F(sched_ue_removal_test, + when_ue_is_being_removed_but_keeps_receiving_sr_indications_then_scheduler_ignores_indications) +{ + // Create UE. + du_ue_index_t ue_index = (du_ue_index_t)test_rgen::uniform_int(0, MAX_DU_UE_INDEX); + rnti_t rnti = to_rnti(test_rgen::uniform_int(0x4601, MAX_CRNTI)); + add_ue(ue_index, rnti); + + // Push BSR update for UE. + this->push_bsr(ul_bsr_indication_message{ + to_du_cell_index(0), ue_index, rnti, bsr_format::SHORT_BSR, ul_bsr_lcg_report_list{{uint_to_lcg_id(0), 100}}}); + + // Wait for at least one UL HARQ to be allocated. + const ul_sched_info* alloc = nullptr; + const unsigned TX_TIMEOUT = 10; + for (unsigned i = 0; i != TX_TIMEOUT; ++i) { + this->run_slot(); + alloc = find_ue_pusch(rnti, *last_sched_res); + if (alloc != nullptr) { + break; + } + } + ASSERT_NE(alloc, nullptr); + + // Schedule UE removal. + rem_ue(ue_index); + + // Keep pushing SRs without ACKing HARQ. + const unsigned UE_REM_TIMEOUT = 1000; + for (unsigned count = 0; count != UE_REM_TIMEOUT and not notif.last_ue_index_deleted.has_value(); ++count) { + this->run_slot(); + ASSERT_EQ(find_ue_pusch(rnti, *last_sched_res), nullptr) << "UE UL allocated despite being marked for removal"; + + const pucch_info* pucch = find_ue_pucch(rnti, *last_sched_res); + if (pucch != nullptr and + (pucch->format == pucch_format::FORMAT_1 and pucch->format_1.sr_bits != sr_nof_bits::no_sr)) { + // UCI indication sets SR indication. + uci_indication uci; + uci.cell_index = to_du_cell_index(0); + uci.slot_rx = last_result_slot(); + uci_indication::uci_pdu::uci_pucch_f0_or_f1_pdu f0; + f0.sr_detected = true; + f0.ul_sinr = 10; + uci.ucis.push_back(uci_indication::uci_pdu{.ue_index = ue_index, .crnti = rnti, .pdu = f0}); + this->sched->handle_uci_indication(uci); + } + } + + ASSERT_TRUE(notif.last_ue_index_deleted.has_value()) << "UE has not been deleted"; + ASSERT_EQ(notif.last_ue_index_deleted, ue_index); +} diff --git a/tests/unittests/scheduler/test_utils/scheduler_output_test_helpers.cpp b/tests/unittests/scheduler/test_utils/scheduler_output_test_helpers.cpp index 69bc3d60c3..ca050e46b2 100644 --- a/tests/unittests/scheduler/test_utils/scheduler_output_test_helpers.cpp +++ b/tests/unittests/scheduler/test_utils/scheduler_output_test_helpers.cpp @@ -21,7 +21,7 @@ */ #include "scheduler_output_test_helpers.h" -#include "lib/scheduler/pdcch_scheduling/pdcch_config_helpers.h" +#include "lib/scheduler/support/pdcch/pdcch_mapping.h" #include "srsran/ran/pdcch/cce_to_prb_mapping.h" #include "srsran/ran/prach/prach_configuration.h" diff --git a/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp b/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp index b4ead12b55..a8b32907c9 100644 --- a/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp @@ -481,6 +481,59 @@ TEST_F(test_pucch_harq_allocator_ded_resources, test_allocate_csi_over_f1_harq) ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f2, slot_grid.result.ul.pucchs[0])); } +TEST_F(test_pucch_harq_allocator_ded_resources, test_allocate_csi_over_f1_harq_2_ues) +{ + const unsigned csi_part1_bits = 4; + pucch_expected_f2.format_2.harq_ack_nof_bits = 1; + pucch_expected_f2.format_2.sr_bits = sr_nof_bits::no_sr; + pucch_expected_f2.format_2.csi_part1_bits = csi_part1_bits; + + // Add a second UE and allocate a PUCCH resource (it'll get assigned a resource with PUCCH resource ind. = 0). + t_bench.add_ue(); + const pucch_harq_ack_grant ue_2_res = + t_bench.pucch_alloc.alloc_ded_pucch_harq_ack_ue(t_bench.res_grid, + t_bench.last_allocated_rnti, + t_bench.get_ue(t_bench.last_allocated_ue_idx).get_pcell().cfg(), + t_bench.k0, + t_bench.k1); + ASSERT_EQ(0, ue_2_res.pucch_res_indicator); + + // Allocate UE 1 a PUCCH resource (it'll get assigned a resource with PUCCH resource ind. = 1). + const pucch_harq_ack_grant ue_1_res = t_bench.pucch_alloc.alloc_ded_pucch_harq_ack_ue( + t_bench.res_grid, t_bench.get_main_ue().crnti, t_bench.get_main_ue().get_pcell().cfg(), t_bench.k0, t_bench.k1); + const unsigned ue_1_pucch_f1_res_indicator = 1; + ASSERT_EQ(ue_1_pucch_f1_res_indicator, ue_1_res.pucch_res_indicator); + + auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; + t_bench.pucch_alloc.pucch_allocate_csi_opportunity( + slot_grid, t_bench.get_main_ue().crnti, t_bench.get_main_ue().get_pcell().cfg(), csi_part1_bits); + + ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); + + // In the following, we need to verify that the PUCCH F2 for CSI preserves the PUCCH resource indicator of the + // previously allocated F1 resource. However, as the PUCCH resource indicator is hidden inside some private function, + // we can't verify this straight away. What we do, instead, we verify that the configuration of the PUCCH grant + // corresponds to the PUCCH F2 resource with PUCCH indicator 1. + const pucch_config& pucch_cfg = + t_bench.get_main_ue().get_pcell().cfg().cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); + const unsigned pucch_harq_f2_res_set_id = 1; + const auto& pucch_f2_res_id = + pucch_cfg.pucch_res_set[pucch_harq_f2_res_set_id].pucch_res_id_list[ue_1_pucch_f1_res_indicator]; + const auto* res_cfg = + std::find_if(pucch_cfg.pucch_res_list.begin(), + pucch_cfg.pucch_res_list.end(), + [pucch_f2_res_id](const pucch_resource& res) { return res.res_id == pucch_f2_res_id; }); + ASSERT_NE(pucch_cfg.pucch_res_list.end(), res_cfg); + const pucch_resource& expected_res_cfg = *res_cfg; + const auto& pucch_csi_grant = slot_grid.result.ul.pucchs[1]; + ASSERT_EQ(expected_res_cfg.format, pucch_csi_grant.format); + ASSERT_EQ(expected_res_cfg.starting_prb, pucch_csi_grant.resources.prbs.start()); + ASSERT_EQ(variant_get(expected_res_cfg.format_params).starting_sym_idx, + pucch_csi_grant.resources.symbols.start()); + ASSERT_EQ(variant_get(expected_res_cfg.format_params).nof_symbols, + pucch_csi_grant.resources.symbols.length()); +} + // Tests whether existing PUCCH HARQ grant gets updated. TEST_F(test_pucch_harq_allocator_ded_resources, test_allocate_csi_over_f1_sr) { diff --git a/tests/unittests/scheduler/uci_and_pucch/uci_allocator_test.cpp b/tests/unittests/scheduler/uci_and_pucch/uci_allocator_test.cpp index a2318c2a19..eefe2e7921 100644 --- a/tests/unittests/scheduler/uci_and_pucch/uci_allocator_test.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/uci_allocator_test.cpp @@ -489,7 +489,7 @@ class test_tdd_uci_allocator : public test_uci_allocator test_tdd_uci_allocator() : test_uci_allocator(test_bench_params{.is_tdd = true}) {} }; -TEST_F(test_tdd_uci_allocator, when_tdd_cfg_then_dai_increases_with_number_of_allocs_and_wraps_around) +TEST_F(test_tdd_uci_allocator, when_tdd_cfg_then_harq_bit_index_increases_with_number_of_allocs) { const std::vector k1_candidates = {static_cast(t_bench.k1)}; for (unsigned i = 0; i != DAI_MOD * 2; ++i) { @@ -500,7 +500,7 @@ TEST_F(test_tdd_uci_allocator, when_tdd_cfg_then_dai_increases_with_number_of_al k1_candidates); if (alloc.alloc_successful) { - ASSERT_EQ(alloc.dai, i % DAI_MOD); + ASSERT_EQ(alloc.harq_bit_idx, i); } } } diff --git a/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.cpp b/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.cpp index 4464d076a9..7b3f062f8b 100644 --- a/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.cpp @@ -135,6 +135,8 @@ test_bench::test_bench(const test_bench_params& params) : { cell_config_builder_params cfg_params{}; cfg_params.csi_rs_enabled = true; + cfg_params.scs_common = params.is_tdd ? subcarrier_spacing::kHz30 : subcarrier_spacing::kHz15; + cfg_params.dl_arfcn = params.is_tdd ? 520000U : 365000U; sched_ue_creation_request_message ue_req = test_helpers::create_default_sched_ue_creation_request(cfg_params); ue_req.ue_index = main_ue_idx; diff --git a/tests/unittests/scheduler/ue_scheduling/CMakeLists.txt b/tests/unittests/scheduler/ue_scheduling/CMakeLists.txt index 8ff9b7ef8d..cfe7a10f3a 100644 --- a/tests/unittests/scheduler/ue_scheduling/CMakeLists.txt +++ b/tests/unittests/scheduler/ue_scheduling/CMakeLists.txt @@ -28,7 +28,8 @@ add_executable(ue_scheduler_test ul_logical_channel_test.cpp ue_pdsch_param_candidate_searcher_test.cpp ue_link_adaptation_controller_test.cpp - ta_manager_test.cpp) + ta_manager_test.cpp + ue_harq_link_adaptation_test.cpp) target_link_libraries(ue_scheduler_test srsran_sched srslog diff --git a/tests/unittests/scheduler/ue_scheduling/harq_entity_test.cpp b/tests/unittests/scheduler/ue_scheduling/harq_entity_test.cpp index b9a1711d3c..d1749a8cd0 100644 --- a/tests/unittests/scheduler/ue_scheduling/harq_entity_test.cpp +++ b/tests/unittests/scheduler/ue_scheduling/harq_entity_test.cpp @@ -48,7 +48,7 @@ TEST(harq_entity, when_all_harqs_are_allocated_harq_entity_cannot_find_empty_har unsigned ack_delay = 4; for (unsigned i = 0; i != nof_harqs; ++i) { - harq_ent.find_empty_dl_harq()->new_tx(sl_tx, ack_delay, 4, 0); + harq_ent.find_empty_dl_harq()->new_tx(sl_tx, ack_delay, 4, 0, 15, 1); harq_ent.find_empty_ul_harq()->new_tx(sl_tx, 4); } ASSERT_EQ(harq_ent.find_empty_dl_harq(), nullptr); @@ -63,7 +63,7 @@ TEST(harq_entity, after_max_ack_wait_timeout_dl_harqs_are_available_for_retx) unsigned ack_delay = 4; for (unsigned i = 0; i != nof_harqs; ++i) { - harq_ent.find_empty_dl_harq()->new_tx(sl_tx, ack_delay, 4, 0); + harq_ent.find_empty_dl_harq()->new_tx(sl_tx, ack_delay, 4, 0, 15, 1); } for (unsigned i = 0; i != max_ack_wait_slots + ack_delay; ++i) { ASSERT_EQ(harq_ent.find_empty_dl_harq(), nullptr); @@ -105,7 +105,7 @@ TEST_F(harq_entity_harq_1bit_tester, when_dtx_received_after_ack_then_dtx_is_ign { unsigned k1 = 4, dai = 0; - this->h_dl.new_tx(next_slot, k1, max_harq_retxs, dai); + this->h_dl.new_tx(next_slot, k1, max_harq_retxs, dai, 15, 1); slot_point pucch_slot = next_slot + k1; while (next_slot != pucch_slot) { @@ -113,14 +113,14 @@ TEST_F(harq_entity_harq_1bit_tester, when_dtx_received_after_ack_then_dtx_is_ign } // ACK received. - ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::ack, dai), nullptr); + ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::ack, dai, nullopt), nullptr); // Reassignment of the HARQ. run_slot(); - this->h_dl.new_tx(next_slot, k1, max_harq_retxs, dai); + this->h_dl.new_tx(next_slot, k1, max_harq_retxs, dai, 15, 1); // DTX received one slot late. - this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::dtx, dai); + this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::dtx, dai, nullopt); } // Note: When two F1 PUCCHs are decoded (one with SR and the other without), there is a small chance that none of them @@ -129,7 +129,9 @@ TEST_F(harq_entity_harq_1bit_tester, when_ack_received_after_nack_then_process_b { unsigned k1 = 4, dai = 0; - this->h_dl.new_tx(next_slot, k1, max_harq_retxs, dai); + this->h_dl.new_tx(next_slot, k1, max_harq_retxs, dai, 15, 1); + this->h_dl.increment_pucch_counter(); + this->h_dl.increment_pucch_counter(); slot_point pucch_slot = next_slot + k1; while (next_slot != pucch_slot) { @@ -137,10 +139,10 @@ TEST_F(harq_entity_harq_1bit_tester, when_ack_received_after_nack_then_process_b } // NACK received. - ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::nack, dai), nullptr); + ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::nack, dai, 1.0F), nullptr); // ACK received. - ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::ack, dai), nullptr); + ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::ack, dai, 2.0F), nullptr); // HARQ should be empty. ASSERT_TRUE(this->h_dl.empty()); @@ -168,11 +170,17 @@ class harq_entity_2_harq_bits_tester : public ::testing::TestWithParam First HARQ, DAI=0. run_slot(); h_dls.push_back(harq_ent.find_empty_dl_harq()); - h_dls[0]->new_tx(next_slot, 5, max_harq_retxs, 0); + h_dls[0]->new_tx(next_slot, 5, max_harq_retxs, 0, 15, 1); + h_dls[0]->increment_pucch_counter(); // > Second HARQ, DAI=1. run_slot(); h_dls.push_back(harq_ent.find_empty_dl_harq()); - h_dls[1]->new_tx(next_slot, 4, max_harq_retxs, 1); + h_dls[1]->new_tx(next_slot, 4, max_harq_retxs, 1, 15, 1); + h_dls[1]->increment_pucch_counter(); + if (GetParam().ack.size() > 1) { + h_dls[0]->increment_pucch_counter(); + h_dls[1]->increment_pucch_counter(); + } pucch_slot = next_slot + 4; @@ -204,26 +212,26 @@ TEST_P(harq_entity_2_harq_bits_tester, handle_pucchs) { auto params = GetParam(); - // First PUCCH, 2 HARQ bits, different DAIs. - harq_ent.dl_ack_info(pucch_slot, (mac_harq_ack_report_status)params.ack[0][0], 0); - harq_ent.dl_ack_info(pucch_slot, (mac_harq_ack_report_status)params.ack[0][1], 1); + // First PUCCH, 2 HARQ bits, different indexes. + harq_ent.dl_ack_info(pucch_slot, (mac_harq_ack_report_status)params.ack[0][0], 0, nullopt); + harq_ent.dl_ack_info(pucch_slot, (mac_harq_ack_report_status)params.ack[0][1], 1, nullopt); - // Second PUCCH, 2 HARQ bits, different DAIs. + // Second PUCCH, 2 HARQ bits, different indexes. if (params.ack.size() > 1) { - harq_ent.dl_ack_info(pucch_slot, (mac_harq_ack_report_status)params.ack[1][0], 0); - harq_ent.dl_ack_info(pucch_slot, (mac_harq_ack_report_status)params.ack[1][1], 1); + harq_ent.dl_ack_info(pucch_slot, (mac_harq_ack_report_status)params.ack[1][0], 0, nullopt); + harq_ent.dl_ack_info(pucch_slot, (mac_harq_ack_report_status)params.ack[1][1], 1, nullopt); } bool check_timeout = false; for (unsigned i = 0; i != params.outcome.size(); ++i) { if (params.outcome[i] == ACKed) { ASSERT_TRUE(h_dls[i]->empty()); - } else if (params.outcome[i] == NACKed) { - ASSERT_TRUE(h_dls[i]->has_pending_retx()); } else { + ASSERT_TRUE(h_dls[i]->has_pending_retx()); + } + + if (params.outcome[i] == DTX_timeout) { // DTX_timeout - ASSERT_FALSE(h_dls[i]->empty()); - ASSERT_FALSE(h_dls[i]->has_pending_retx()); check_timeout = true; } } @@ -250,8 +258,8 @@ INSTANTIATE_TEST_SUITE_P( test_2_harq_bits_params{.ack = {{2, 1}}, .outcome = {DTX_timeout, ACKed}}, test_2_harq_bits_params{.ack = {{1, 1}, {2, 2}}, .outcome = {ACKed, ACKed}}, test_2_harq_bits_params{.ack = {{0, 0}, {2, 2}}, .outcome = {NACKed, NACKed}}, - test_2_harq_bits_params{.ack = {{2, 2}, {2, 1}}, .outcome = {DTX_timeout, ACKed}}, - test_2_harq_bits_params{.ack = {{2, 2}, {2, 2}}, .outcome = {DTX_timeout, DTX_timeout}})); + test_2_harq_bits_params{.ack = {{2, 2}, {2, 1}}, .outcome = {NACKed, ACKed}}, + test_2_harq_bits_params{.ack = {{2, 2}, {2, 2}}, .outcome = {NACKed, NACKed}})); class harq_entity_harq_5bit_tester : public ::testing::Test { @@ -279,12 +287,12 @@ class harq_entity_harq_5bit_tester : public ::testing::Test TEST_F(harq_entity_harq_5bit_tester, when_5_harq_bits_are_acks_then_all_5_active_harqs_are_updated) { - const unsigned active_harqs = 5, dai_mod = 4, k1 = 4; + const unsigned active_harqs = 5, k1 = 4; std::vector h_dls(active_harqs); for (unsigned i = 0; i != active_harqs; ++i) { h_dls[i] = harq_ent.find_empty_dl_harq(); - h_dls[i]->new_tx(next_slot, k1, max_harq_retxs, i % dai_mod); + h_dls[i]->new_tx(next_slot, k1, max_harq_retxs, i, 15, 1); } slot_point pucch_slot = next_slot + k1; @@ -294,7 +302,7 @@ TEST_F(harq_entity_harq_5bit_tester, when_5_harq_bits_are_acks_then_all_5_active // ACK received. for (unsigned i = 0; i != active_harqs; ++i) { - ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::ack, i % dai_mod), nullptr); + ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::ack, i, nullopt), nullptr); } for (unsigned i = 0; i != h_dls.size(); ++i) { @@ -304,12 +312,12 @@ TEST_F(harq_entity_harq_5bit_tester, when_5_harq_bits_are_acks_then_all_5_active TEST_F(harq_entity_harq_5bit_tester, when_5_harq_bits_are_nacks_then_all_5_active_harqs_are_updated) { - const unsigned active_harqs = 5, dai_mod = 4, k1 = 4; + const unsigned active_harqs = 5, k1 = 4; std::vector h_dls(active_harqs); for (unsigned i = 0; i != active_harqs; ++i) { h_dls[i] = harq_ent.find_empty_dl_harq(); - h_dls[i]->new_tx(next_slot, k1, max_harq_retxs, i % dai_mod); + h_dls[i]->new_tx(next_slot, k1, max_harq_retxs, i, 15, 1); } slot_point pucch_slot = next_slot + k1; @@ -319,7 +327,7 @@ TEST_F(harq_entity_harq_5bit_tester, when_5_harq_bits_are_nacks_then_all_5_activ // NACK received. for (unsigned i = 0; i != active_harqs; ++i) { - ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::nack, i % dai_mod), nullptr); + ASSERT_NE(this->harq_ent.dl_ack_info(pucch_slot, srsran::mac_harq_ack_report_status::nack, i, nullopt), nullptr); } for (unsigned i = 0; i != h_dls.size(); ++i) { diff --git a/tests/unittests/scheduler/ue_scheduling/harq_process_test.cpp b/tests/unittests/scheduler/ue_scheduling/harq_process_test.cpp index 7fe0c6066f..92e016eeac 100644 --- a/tests/unittests/scheduler/ue_scheduling/harq_process_test.cpp +++ b/tests/unittests/scheduler/ue_scheduling/harq_process_test.cpp @@ -22,6 +22,7 @@ #include "lib/scheduler/ue_scheduling/harq_process.h" #include "srsran/scheduler/scheduler_slot_handler.h" +#include "srsran/support/test_utils.h" #include using namespace srsran; @@ -80,7 +81,7 @@ TEST_F(dl_harq_process_tester, newtx_set_harq_to_not_empty) unsigned k1 = 4, max_harq_retxs = 5, tbs_bytes = 1000; sch_mcs_index mcs = 10; - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); + h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0, 15, 1); ASSERT_FALSE(h_dl.empty()); ASSERT_FALSE(h_dl.empty(0)); ASSERT_TRUE(h_dl.empty(1)); @@ -113,7 +114,7 @@ TEST_F(dl_harq_process_tester, retx_of_empty_harq_asserts) TEST_F(dl_harq_process_tester, ack_of_empty_harq_is_noop) { - ASSERT_FALSE(h_dl.ack_info(0, mac_harq_ack_report_status::ack)) << "ACK of empty HARQ should fail"; + ASSERT_FALSE(h_dl.ack_info(0, mac_harq_ack_report_status::ack, nullopt)) << "ACK of empty HARQ should fail"; } class dl_harq_process_timeout_tester : public dl_harq_process_tester @@ -127,13 +128,13 @@ TEST_F(dl_harq_process_timeout_tester, when_max_retx_exceeded_and_nack_is_receiv unsigned k1 = 1, max_harq_retxs = 1; slot_point sl_tx{0, 0}; - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); + h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0, 15, 1); h_dl.slot_indication(++sl_tx); ASSERT_FALSE(h_dl.has_pending_retx(0)); - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::nack)); + ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::nack, nullopt)); h_dl.new_retx(sl_tx, k1, 0); h_dl.slot_indication(++sl_tx); - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::nack)); + ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::nack, nullopt)); ASSERT_TRUE(h_dl.empty()); ASSERT_FALSE(h_dl.has_pending_retx()); } @@ -145,9 +146,9 @@ TEST_F(dl_harq_process_timeout_tester, when_harq_has_no_pending_retx_calling_new unsigned k1 = 1, max_harq_retxs = 1; slot_point sl_tx{0, 0}; - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); + h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0, 15, 1); ASSERT_TRUE(not h_dl.empty(0) and not h_dl.has_pending_retx(0)); - ASSERT_DEATH(h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0), ".*"); + ASSERT_DEATH(h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0, 15, 1), ".*"); ASSERT_DEATH(h_dl.new_retx(sl_tx, k1, 0), ".*"); } #endif @@ -186,20 +187,20 @@ class dl_harq_process_param_tester : public ::testing::TestWithParammax_ack_wait_slots + this->k1; ++i) { ASSERT_FALSE(h_dl.empty()) << "It is too early for HARQ to be reset"; @@ -227,7 +228,7 @@ TEST_P(dl_harq_process_param_tester, when_ack_rx_wait_time_elapsed_harq_is_avail TEST_P(dl_harq_process_param_tester, harq_newtxs_flip_ndi) { - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); + h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0, 15, 1); for (unsigned i = 0; i != this->max_ack_wait_slots + k1 - 1; ++i) { ASSERT_FALSE(h_dl.empty()); ASSERT_FALSE(h_dl.has_pending_retx()); @@ -235,8 +236,8 @@ TEST_P(dl_harq_process_param_tester, harq_newtxs_flip_ndi) } bool prev_ndi = h_dl.tb(0).ndi; - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::ack)); - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); + ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::ack, nullopt)); + h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0, 15, 1); ASSERT_NE(prev_ndi, h_dl.tb(0).ndi); } @@ -246,18 +247,21 @@ INSTANTIATE_TEST_SUITE_P(dl_harq_param_combine, testing::Values(2, 4, 6, 8), // max_ack_wait_slots testing::Values(1, 2, 4, 6))); // k1 -class dl_harq_process_param_tester_dtx : public ::testing::Test +class base_dl_harq_process_multi_harq_ack_test { -protected: - dl_harq_process_param_tester_dtx() : - max_harq_retxs(1), - max_ack_wait_slots(12), - k1(1), +public: + base_dl_harq_process_multi_harq_ack_test() : dl_logger(srslog::fetch_basic_logger("SCHED"), to_rnti(0x4601), to_du_cell_index(0), true), h_dl(to_harq_id(0), dl_logger, {timeout_handler, to_du_ue_index(0)}, max_ack_wait_slots) { srslog::init(); + + // Allocate HARQ expecting two PUCCHs. + h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0, 15, 1); + h_dl.increment_pucch_counter(); + h_dl.increment_pucch_counter(); } + ~base_dl_harq_process_multi_harq_ack_test() { srslog::flush(); } void slot_indication() { @@ -266,12 +270,17 @@ class dl_harq_process_param_tester_dtx : public ::testing::Test h_dl.slot_indication(sl_tx); } - ~dl_harq_process_param_tester_dtx() { srslog::flush(); } + static mac_harq_ack_report_status get_random_harq_ack() + { + return static_cast(test_rgen::uniform_int(0, 2)); + } - const unsigned max_harq_retxs; - const unsigned max_ack_wait_slots; - const unsigned shortened_ack_wait_slots{10}; - const unsigned k1; + const unsigned max_harq_retxs = 1; + const unsigned max_ack_wait_slots = 12; + const unsigned shortened_ack_wait_slots{4}; + const unsigned k1 = 1; + const unsigned first_ack_slot = 1; + const unsigned second_ack_slot = 2; harq_logger dl_logger; dummy_harq_timeout_handler timeout_handler; @@ -279,142 +288,93 @@ class dl_harq_process_param_tester_dtx : public ::testing::Test slot_point sl_tx{0, 0}; }; -TEST_F(dl_harq_process_param_tester_dtx, test_dtx) +static float random_snr() { - const unsigned dtx_slot = 1; - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); + return static_cast(std::uniform_real_distribution{-20.0F, 30.0F}(test_rgen::get())); +} + +class dl_harq_process_multi_harq_ack_timeout_test : public base_dl_harq_process_multi_harq_ack_test, + public ::testing::Test +{}; + +TEST_F(dl_harq_process_multi_harq_ack_timeout_test, + when_one_harq_ack_is_received_and_other_goes_missing_then_harq_timeout_is_shortened) +{ + const mac_harq_ack_report_status ack_val = get_random_harq_ack(); + for (unsigned i = 0; i != max_ack_wait_slots + k1 + 1; ++i) { // Notify HARQ process with DTX (ACK not decoded). - if (i == dtx_slot) { - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::dtx)); + if (i == first_ack_slot) { + ASSERT_TRUE(h_dl.ack_info(0, ack_val, random_snr())); } // Before reaching the ack_wait_slots, the HARQ should be neither empty nor have pending reTX. if (i < shortened_ack_wait_slots + k1) { ASSERT_FALSE(h_dl.empty()); ASSERT_FALSE(h_dl.has_pending_retx()); + ASSERT_TRUE(h_dl.is_waiting_ack()); ASSERT_EQ(timeout_handler.last_ue_index, INVALID_DU_UE_INDEX); } // Once the shortened_ack_wait_slots has passed, expect pending reTXs. else { - ASSERT_FALSE(h_dl.empty()); - ASSERT_TRUE(h_dl.has_pending_retx()); - ASSERT_EQ(timeout_handler.last_ue_index, to_du_ue_index(0)); - ASSERT_TRUE(timeout_handler.last_dir_is_dl); + if (ack_val == srsran::mac_harq_ack_report_status::ack) { + ASSERT_TRUE(h_dl.empty()); + ASSERT_NE(timeout_handler.last_ue_index, to_du_ue_index(0)); + } else { + ASSERT_TRUE(h_dl.has_pending_retx()); + ASSERT_EQ(timeout_handler.last_ue_index, to_du_ue_index(0)); + ASSERT_TRUE(timeout_handler.last_dir_is_dl); + } + break; } slot_indication(); } } -TEST_F(dl_harq_process_param_tester_dtx, test_dtx_ack) -{ - // DTX arrives first, then NACK. - const unsigned dtx_slot = 1; - const unsigned ack_slot = 2; - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); - for (unsigned i = 0; i != max_ack_wait_slots + k1 + 1; ++i) { - if (i == dtx_slot) { - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::dtx)); - } - if (i == ack_slot) { - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::ack)); - } - - // Before ACK, the process is waiting for an ACK. - // NOTE: The DTX will only change the ack_wait_in_slots, with no effect on pending transmissions. - if (i < ack_slot) { - ASSERT_FALSE(h_dl.empty()); - ASSERT_FALSE(h_dl.has_pending_retx()); - } - // When ACK arrives, the process will be emptied. NOTE: DTX won't do anything in this case. - else { - ASSERT_TRUE(h_dl.empty()); - } - slot_indication(); - } -} +struct multi_ack_test_params { + std::array ack; + std::array snr; + bool outcome; +}; -TEST_F(dl_harq_process_param_tester_dtx, test_ack_dtx) +void PrintTo(const multi_ack_test_params& params, ::std::ostream* os) { - // ACK arrives first, then DTX. - const unsigned ack_slot = 1; - const unsigned dtx_slot = 2; - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); - for (unsigned i = 0; i != max_ack_wait_slots + k1 + 1; ++i) { - if (i == ack_slot) { - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::ack)); - } - if (i == dtx_slot) { - h_dl.ack_info(0, mac_harq_ack_report_status::dtx); - } - - // Before ACK, the process is waiting for an ACK. - if (i < ack_slot) { - ASSERT_FALSE(h_dl.empty()); - ASSERT_FALSE(h_dl.has_pending_retx()); - } - // When ACK arrives, the process will be emptied. NOTE: DTX won't do anything in this case. - else { - ASSERT_TRUE(h_dl.empty()); - } - slot_indication(); - } - - ASSERT_EQ(timeout_handler.last_ue_index, INVALID_DU_UE_INDEX) << "Timeout should not expire"; + *os << fmt::format("{{ack={} snr={:.2}}} + {{ack={} snr={:.2}}} -> outcome={}", + params.ack[0], + params.snr[0], + params.ack[1], + params.snr[1], + params.outcome ? "ACK" : "NACK"); } -TEST_F(dl_harq_process_param_tester_dtx, test_dtx_nack) -{ - // DTX arrives first, then NACK. - const unsigned dtx_slot = 1; - const unsigned nack_slot = 2; - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); - for (unsigned i = 0; i != max_ack_wait_slots + k1 + 1; ++i) { - if (i == dtx_slot) { - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::dtx)); - } - if (i == nack_slot) { - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::nack)); - } +class dl_harq_process_multi_harq_ack_test : public base_dl_harq_process_multi_harq_ack_test, + public ::testing::TestWithParam +{}; - // Before NACK, the process is waiting for an ACK. - // NOTE: The DTX will only change the ack_wait_in_slots, with no effect on pending transmissions. - if (i < nack_slot) { - ASSERT_FALSE(h_dl.empty()); - ASSERT_FALSE(h_dl.has_pending_retx()); - } - // When NACK arrives, the process has pending_retx. - else { - ASSERT_TRUE(h_dl.has_pending_retx()); - } - slot_indication(); - } - - ASSERT_EQ(timeout_handler.last_ue_index, INVALID_DU_UE_INDEX) << "Timeout should not expire"; -} - -TEST_F(dl_harq_process_param_tester_dtx, test_nack_dtx) +TEST_P(dl_harq_process_multi_harq_ack_test, two_harq_acks_received) { - // NACK arrives first, then DTX. - const unsigned nack_slot = 1; - const unsigned dtx_slot = 2; - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); + auto params = GetParam(); + for (unsigned i = 0; i != max_ack_wait_slots + k1 + 1; ++i) { - if (i == nack_slot) { - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::nack)); + if (i == first_ack_slot) { + ASSERT_TRUE(h_dl.ack_info(0, static_cast(params.ack[0]), params.snr[0])); } - if (i == dtx_slot) { - h_dl.ack_info(0, mac_harq_ack_report_status::dtx); + if (i == second_ack_slot) { + ASSERT_TRUE(h_dl.ack_info(0, static_cast(params.ack[1]), params.snr[1])); } - // Before NACK, the process is waiting for an ACK. - if (i < nack_slot) { - ASSERT_FALSE(h_dl.empty()); - ASSERT_FALSE(h_dl.has_pending_retx()); - } - // When NACK arrives, the process has pending_retx. NOTE: DTX won't do anything in this case. - else { - ASSERT_TRUE(h_dl.has_pending_retx()); + if (i < second_ack_slot) { + // Before second HARQ-ACK, the process is waiting for an ACK. + ASSERT_TRUE(h_dl.is_waiting_ack()); + } else { + // When second HARQ-ACK arrives, the process should be set as either empty or pending reTX. + ASSERT_FALSE(h_dl.is_waiting_ack()); + if (params.outcome) { + ASSERT_TRUE(h_dl.empty()); + } else { + ASSERT_TRUE(h_dl.has_pending_retx()); + } + break; } slot_indication(); } @@ -422,31 +382,15 @@ TEST_F(dl_harq_process_param_tester_dtx, test_nack_dtx) ASSERT_EQ(timeout_handler.last_ue_index, INVALID_DU_UE_INDEX) << "Timeout should not expire"; } -// Note: Sometimes, the two F1 PUCCHs (one with SR) are detected. -TEST_F(dl_harq_process_param_tester_dtx, test_nack_ack) -{ - const unsigned nack_slot = 2; - const unsigned ack_slot = 2; - h_dl.new_tx(sl_tx, k1, max_harq_retxs, 0); - for (unsigned i = 0; i != max_ack_wait_slots + k1 + 1; ++i) { - if (i == nack_slot) { - ASSERT_TRUE(h_dl.ack_info(0, mac_harq_ack_report_status::nack)); - } - if (i == ack_slot) { - h_dl.ack_info(0, mac_harq_ack_report_status::ack); - } - - // Before NACK, the process is waiting for an ACK. - if (i < nack_slot) { - ASSERT_FALSE(h_dl.empty()); - ASSERT_FALSE(h_dl.has_pending_retx()); - } - // When NACK+ACK arrives, the process becomes empty. - else { - ASSERT_TRUE(h_dl.empty()); - } - slot_indication(); - } - - ASSERT_EQ(timeout_handler.last_ue_index, INVALID_DU_UE_INDEX) << "Timeout should not expire"; -} \ No newline at end of file +INSTANTIATE_TEST_SUITE_P( + dl_harq_process_tester, + dl_harq_process_multi_harq_ack_test, + testing::Values(multi_ack_test_params{.ack = {2, 1}, .snr = {random_snr(), random_snr()}, .outcome = true}, + multi_ack_test_params{.ack = {1, 2}, .snr = {random_snr(), random_snr()}, .outcome = true}, + multi_ack_test_params{.ack = {2, 0}, .snr = {random_snr(), random_snr()}, .outcome = false}, + multi_ack_test_params{.ack = {0, 2}, .snr = {random_snr(), random_snr()}, .outcome = false}, + multi_ack_test_params{.ack = {0, 1}, .snr = {10.0, 11.0}, .outcome = true}, + multi_ack_test_params{.ack = {0, 1}, .snr = {10.0, 9.0}, .outcome = false}, + multi_ack_test_params{.ack = {2, 2}, .snr = {random_snr(), random_snr()}, .outcome = false}, + multi_ack_test_params{.ack = {0, 0}, .snr = {random_snr(), random_snr()}, .outcome = false}, + multi_ack_test_params{.ack = {1, 1}, .snr = {random_snr(), random_snr()}, .outcome = true})); diff --git a/tests/unittests/scheduler/ue_scheduling/ue_grid_allocator_test.cpp b/tests/unittests/scheduler/ue_scheduling/ue_grid_allocator_test.cpp index ef06ff92be..b420f50ef9 100644 --- a/tests/unittests/scheduler/ue_scheduling/ue_grid_allocator_test.cpp +++ b/tests/unittests/scheduler/ue_scheduling/ue_grid_allocator_test.cpp @@ -108,7 +108,7 @@ TEST_F(ue_grid_allocator_tester, when_coreset0_grant_inside_coreset0_rb_lims_the .aggr_lvl = aggregation_level::n4}; set_allocator_responses(grant); - ASSERT_TRUE(alloc.allocate_dl_grant(grant)); + ASSERT_EQ(alloc.allocate_dl_grant(grant), alloc_outcome::success); } TEST_F(ue_grid_allocator_tester, @@ -138,7 +138,7 @@ TEST_F(ue_grid_allocator_tester, .aggr_lvl = aggregation_level::n4}; set_allocator_responses(grant); - ASSERT_TRUE(alloc.allocate_dl_grant(grant)); + ASSERT_EQ(alloc.allocate_dl_grant(grant), alloc_outcome::success); } TEST_F(ue_grid_allocator_tester, when_using_fallback_dci_format_only_64_qam_mcs_table_is_used) @@ -161,7 +161,7 @@ TEST_F(ue_grid_allocator_tester, when_using_fallback_dci_format_only_64_qam_mcs_ .aggr_lvl = aggregation_level::n4}; set_allocator_responses(grant); - ASSERT_TRUE(alloc.allocate_dl_grant(grant)); + ASSERT_EQ(alloc.allocate_dl_grant(grant), alloc_outcome::success); ASSERT_EQ(res_grid[0].result.dl.ue_grants.back().pdsch_cfg.codewords.back().mcs_table, srsran::pdsch_mcs_table::qam64); } @@ -186,7 +186,7 @@ TEST_F(ue_grid_allocator_tester, when_using_non_fallback_dci_format_use_mcs_tabl .aggr_lvl = aggregation_level::n4}; set_allocator_responses(grant); - ASSERT_TRUE(alloc.allocate_dl_grant(grant)); + ASSERT_EQ(alloc.allocate_dl_grant(grant), alloc_outcome::success); ASSERT_EQ(res_grid[0].result.dl.ue_grants.back().pdsch_cfg.codewords.back().mcs_table, srsran::pdsch_mcs_table::qam256); } diff --git a/tests/unittests/scheduler/ue_scheduling/ue_harq_link_adaptation_test.cpp b/tests/unittests/scheduler/ue_scheduling/ue_harq_link_adaptation_test.cpp new file mode 100644 index 0000000000..ff4094077c --- /dev/null +++ b/tests/unittests/scheduler/ue_scheduling/ue_harq_link_adaptation_test.cpp @@ -0,0 +1,155 @@ +/* + * + * Copyright 2021-2023 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "../test_utils/config_generators.h" +#include "../test_utils/dummy_test_components.h" +#include + +using namespace srsran; + +class ue_harq_link_adaptation_test : public ::testing::Test +{ +protected: + ue_harq_link_adaptation_test() : logger(srslog::fetch_basic_logger("SCHED", true)) + { + logger.set_level(srslog::basic_levels::debug); + srslog::init(); + + // Set nof. DL ports to 4. + cell_config_builder_params params{}; + params.nof_dl_ports = 4; + const sched_cell_configuration_request_message sched_cell_cfg_req = + test_helpers::make_default_sched_cell_configuration_request(params); + + const scheduler_ue_expert_config& expert_cfg{sched_cfg.ue}; + cell_cfg.emplace(sched_cfg, sched_cell_cfg_req); + + next_slot = test_helpers::generate_random_slot_point(cell_cfg->dl_cfg_common.init_dl_bwp.generic_params.scs); + + // Create UE. + sched_ue_creation_request_message ue_creation_req = test_helpers::create_default_sched_ue_creation_request(params); + ue_creation_req.ue_index = to_du_ue_index(0); + ue_creation_req.crnti = to_rnti(0x4601 + (unsigned)ue_creation_req.ue_index); + for (const lcid_t lcid : std::array{uint_to_lcid(1), uint_to_lcid(2), uint_to_lcid(4)}) { + ue_creation_req.cfg.lc_config_list->push_back(config_helpers::create_default_logical_channel_config(lcid)); + } + ue_ptr = std::make_unique(expert_cfg, *cell_cfg, ue_creation_req, harq_timeout_handler); + ue_cc = &ue_ptr->get_cell(to_ue_cell_index(0)); + } + + void run_slot() + { + next_slot++; + ue_ptr->slot_indication(next_slot); + } + + void handle_harq_newtx(harq_id_t harq_id, unsigned k1 = 4) + { + const search_space_info& ss = ue_cc->cfg().search_space(to_search_space_id(2)); + + const pdsch_information pdsch{ue_ptr->crnti, + &ss.bwp->dl_common->generic_params, + ss.coreset, + vrb_alloc{vrb_interval{0, 5}}, + ss.pdsch_time_domain_list[0].symbols, + {}, + {}, + ue_cc->cfg().cell_cfg_common.pci, + 2, + false, + search_space_set_type::ue_specific, + dci_dl_format::f1_1, + harq_id, + nullopt}; + + ue_cc->harqs.dl_harq(harq_id).new_tx(next_slot, k1, 4, 0, 15, 2); + ue_cc->harqs.dl_harq(harq_id).save_alloc_params(dci_dl_rnti_config_type::c_rnti_f1_1, pdsch); + } + + const scheduler_expert_config sched_cfg = config_helpers::make_default_scheduler_expert_config(); + optional cell_cfg; + scheduler_harq_timeout_dummy_handler harq_timeout_handler; + + srslog::basic_logger& logger; + + std::unique_ptr ue_ptr; + ue_cell* ue_cc = nullptr; + + slot_point next_slot; +}; + +TEST_F(ue_harq_link_adaptation_test, harq_not_retx_when_cqi_drops_below_threshold) +{ + // Action: UE reports CQI value of 15. + csi_report_data csi{}; + csi.first_tb_wideband_cqi = cqi_value{15}; + ue_cc->handle_csi_report(csi); + + static const harq_id_t harq_id = to_harq_id(0); + handle_harq_newtx(harq_id); + + const dl_harq_process& h = ue_cc->harqs.dl_harq(harq_id); + + while (h.slot_ack() != next_slot) { + // Run until ACK slot. + run_slot(); + } + + // Action: UE reports CQI value of 10 (CQI during new Tx was 15). + csi.first_tb_wideband_cqi = cqi_value{10}; + ue_cc->handle_csi_report(csi); + + // Action: NACK the HARQ. + ue_cc->harqs.dl_harq(harq_id).ack_info(0, mac_harq_ack_report_status::nack, nullopt); + + // Result: There should not be retx for HARQ. + ASSERT_EQ(ue_cc->harqs.find_pending_dl_retx(), nullptr) << "HARQ must not be retransmitted due to drop in CQI"; +} + +TEST_F(ue_harq_link_adaptation_test, harq_not_retx_when_ri_drops_below_threshold) +{ + // Action: UE reports RI value of 2. + csi_report_data csi{}; + csi.first_tb_wideband_cqi = cqi_value{15}; + csi.ri = 2; + ue_cc->handle_csi_report(csi); + + static const harq_id_t harq_id = to_harq_id(0); + handle_harq_newtx(harq_id); + + const dl_harq_process& h = ue_cc->harqs.dl_harq(harq_id); + + while (h.slot_ack() != next_slot) { + // Run until ACK slot. + run_slot(); + } + + // Action: UE reports RI value of 1. + csi.ri = 1; + ue_cc->handle_csi_report(csi); + + // Action: NACK the HARQ. + ue_cc->harqs.dl_harq(harq_id).ack_info(0, mac_harq_ack_report_status::nack, nullopt); + + // Result: There should not be retx for HARQ. + ASSERT_EQ(ue_cc->harqs.find_pending_dl_retx(), nullptr) << "HARQ must not be retransmitted due to drop in RI"; +} diff --git a/tests/unittests/scheduler/ue_scheduling/ue_pdsch_param_candidate_searcher_test.cpp b/tests/unittests/scheduler/ue_scheduling/ue_pdsch_param_candidate_searcher_test.cpp index 19cb6b6180..8f8e074558 100644 --- a/tests/unittests/scheduler/ue_scheduling/ue_pdsch_param_candidate_searcher_test.cpp +++ b/tests/unittests/scheduler/ue_scheduling/ue_pdsch_param_candidate_searcher_test.cpp @@ -52,7 +52,7 @@ class ue_pdsch_param_candidate_searcher_test : public ::testing::Test void handle_harq_newtx(harq_id_t harq_id, unsigned k1 = 4) { - const search_space_info& ss = ue_cc->cfg().search_space(to_search_space_id(1)); + const search_space_info& ss = ue_cc->cfg().search_space(to_search_space_id(2)); pdsch_information pdsch{ue_ptr->crnti, &ss.bwp->dl_common->generic_params, @@ -64,13 +64,13 @@ class ue_pdsch_param_candidate_searcher_test : public ::testing::Test 0, 1, false, - search_space_set_type::type1, - dci_dl_format::f1_0, + search_space_set_type::ue_specific, + dci_dl_format::f1_1, harq_id, nullopt}; - ue_cc->harqs.dl_harq(harq_id).new_tx(next_slot, k1, 4, 0); - ue_cc->harqs.dl_harq(harq_id).save_alloc_params(srsran::dci_dl_rnti_config_type::c_rnti_f1_0, pdsch); + ue_cc->harqs.dl_harq(harq_id).new_tx(next_slot, k1, 4, 0, 15, 1); + ue_cc->harqs.dl_harq(harq_id).save_alloc_params(srsran::dci_dl_rnti_config_type::c_rnti_f1_1, pdsch); } const scheduler_expert_config sched_cfg = config_helpers::make_default_scheduler_expert_config(); @@ -140,7 +140,7 @@ TEST_F(ue_pdsch_param_candidate_searcher_test, when_harqs_with_pending_retx_exis // Action: NACK the HARQs. for (unsigned hid : harq_ids) { - ue_cc->harqs.dl_harq(to_harq_id(hid)).ack_info(0, srsran::mac_harq_ack_report_status::nack); + ue_cc->harqs.dl_harq(to_harq_id(hid)).ack_info(0, srsran::mac_harq_ack_report_status::nack, nullopt); EXPECT_TRUE(ue_cc->harqs.dl_harq(to_harq_id(hid)).has_pending_retx()); } diff --git a/tests/unittests/srsvec/srsvec_bit_test.cpp b/tests/unittests/srsvec/srsvec_bit_test.cpp index 0c35a9997a..6cb6c0eb38 100644 --- a/tests/unittests/srsvec/srsvec_bit_test.cpp +++ b/tests/unittests/srsvec/srsvec_bit_test.cpp @@ -60,8 +60,8 @@ void test_unpack_vector(unsigned N) std::uniform_int_distribution dist(0, UINT8_MAX); // Create random value to unpack - srsvec::aligned_vec packed(nbytes); - for (uint8_t& value : packed) { + dynamic_bit_buffer packed(nbits); + for (uint8_t& value : packed.get_buffer()) { value = dist(rgen); } @@ -70,15 +70,10 @@ void test_unpack_vector(unsigned N) // Generate expected values. srsvec::aligned_vec expected(nbits); - std::generate(expected.begin(), expected.end(), [&, index = 0]() mutable { - unsigned byte_idx = index / 8; - unsigned bit_idx = index % 8; - ++index; - return ((unsigned)packed[byte_idx] >> (7U - bit_idx)) & 1U; - }); + std::generate(expected.begin(), expected.end(), [&, index = 0]() mutable { return packed.extract(index++, 1); }); // Unpack - srsvec::bit_unpack(span(unpacked), span(packed)); + srsvec::bit_unpack(unpacked, packed); // Assert each bit TESTASSERT_EQ(span(expected), span(unpacked)); @@ -122,16 +117,14 @@ void test_pack_vector(unsigned N) } // Create destination - srsvec::aligned_vec packed(nbytes); + dynamic_bit_buffer packed(nbits); // Unpack srsvec::bit_pack(packed, unpacked); // Assert each bit for (unsigned i = 0; i != nbits; i++) { - unsigned byte_idx = i / 8; - unsigned bit_idx = i % 8; - uint8_t gold = (packed[byte_idx] >> (7U - bit_idx)) & 1U; + uint8_t gold = packed.extract(i, 1); TESTASSERT_EQ(gold, unpacked[i]); } } diff --git a/tests/unittests/support/CMakeLists.txt b/tests/unittests/support/CMakeLists.txt index fe356a6814..22f6951345 100644 --- a/tests/unittests/support/CMakeLists.txt +++ b/tests/unittests/support/CMakeLists.txt @@ -35,7 +35,7 @@ add_executable(function_signature_test function_signature_test.cpp) add_test(function_signature_test function_signature_test) add_executable(async_event_test async_event_test.cpp) -target_link_libraries(async_event_test srslog) +target_link_libraries(async_event_test srslog gtest gtest_main) add_test(async_event_test async_event_test) add_executable(async_task_test async_task_test.cpp) @@ -50,9 +50,9 @@ add_executable(async_queue_test async_queue_test.cpp) target_link_libraries(async_queue_test srslog srsran_support gtest gtest_main) add_test(async_queue_test async_queue_test) -add_executable(async_task_sequencer_test async_task_sequencer_test.cpp) -target_link_libraries(async_task_sequencer_test srslog srsran_support) -add_test(async_task_sequencer_test async_task_sequencer_test) +add_executable(fifo_async_task_scheduler_test fifo_async_task_scheduler_test.cpp) +target_link_libraries(fifo_async_task_scheduler_test srslog srsran_support) +add_test(fifo_async_task_scheduler_test fifo_async_task_scheduler_test) add_executable(unique_thread_test unique_thread_test.cpp) target_link_libraries(unique_thread_test srslog srsran_support) diff --git a/tests/unittests/support/async_event_test.cpp b/tests/unittests/support/async_event_test.cpp index 10623e8435..1ecd8a6a88 100644 --- a/tests/unittests/support/async_event_test.cpp +++ b/tests/unittests/support/async_event_test.cpp @@ -21,13 +21,15 @@ */ #include "srsran/support/async/eager_async_task.h" +#include "srsran/support/async/event_sender_receiver.h" #include "srsran/support/async/manual_event.h" #include "srsran/support/test_utils.h" +#include using namespace srsran; /// Test manual event flag -void test_manual_event_flag() +TEST(manual_event_flag_test, test_all) { manual_event_flag event; TESTASSERT(not event.is_set()); @@ -54,7 +56,7 @@ void test_manual_event_flag() } /// Test manual event -void test_manual_event() +TEST(manual_event_test, test_all) { manual_event event; TESTASSERT(not event.is_set()); @@ -78,8 +80,86 @@ void test_manual_event() TESTASSERT_EQ(5, t.get()); } -int main() +TEST(event_sender_receiver_test, test_receiver_no_sender) { - test_manual_event_flag(); - test_manual_event(); -} \ No newline at end of file + event_receiver rx; + ASSERT_FALSE(rx.completed()); + ASSERT_FALSE(rx.successful()); + ASSERT_FALSE(rx.aborted()); +} + +TEST(event_sender_receiver_test, test_sender_set) +{ + event_receiver rx; + event_sender tx = rx.get_sender(); + + ASSERT_FALSE(rx.completed()); + ASSERT_FALSE(rx.successful()); + ASSERT_FALSE(rx.aborted()); + + tx.set(5); + + ASSERT_TRUE(rx.completed()); + ASSERT_TRUE(rx.successful()); + ASSERT_FALSE(rx.aborted()); + ASSERT_EQ(rx.result(), 5); +} + +TEST(event_sender_receiver_test, test_sender_cancel) +{ + event_receiver rx; + + { + event_sender tx = rx.get_sender(); + ASSERT_FALSE(rx.aborted()); + } + ASSERT_TRUE(rx.completed()); + ASSERT_TRUE(rx.aborted()); + ASSERT_FALSE(rx.successful()); +} + +TEST(event_sender_receiver_test, await_event_receiver) +{ + manual_event_flag finished_ev; + event_receiver rx; + event_sender tx = rx.get_sender(); + + eager_async_task t = + launch_async([&finished_ev, tx = std::move(tx)](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + CORO_AWAIT(finished_ev); + tx.set(5); + CORO_RETURN(); + }); + + ASSERT_FALSE(t.ready()); + ASSERT_FALSE(rx.completed()); + + finished_ev.set(); + ASSERT_TRUE(t.ready()); + ASSERT_TRUE(rx.completed()); + ASSERT_TRUE(rx.successful()); + ASSERT_EQ(rx.result(), 5); +} + +TEST(event_sender_receiver_test, await_event_receiver_and_sender_cancelled) +{ + manual_event_flag finished_ev; + event_receiver rx; + event_sender tx = rx.get_sender(); + + { + eager_async_task t = + launch_async([&finished_ev, tx = std::move(tx)](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + CORO_AWAIT(finished_ev); + tx.set(5); + CORO_RETURN(); + }); + } + + ASSERT_FALSE(finished_ev.is_set()); + ASSERT_TRUE(rx.completed()); + ASSERT_FALSE(rx.successful()); + ASSERT_TRUE(rx.aborted()); +} diff --git a/tests/unittests/support/async_task_sequencer_test.cpp b/tests/unittests/support/fifo_async_task_scheduler_test.cpp similarity index 91% rename from tests/unittests/support/async_task_sequencer_test.cpp rename to tests/unittests/support/fifo_async_task_scheduler_test.cpp index 7eb9351c7e..de81d45aad 100644 --- a/tests/unittests/support/async_task_sequencer_test.cpp +++ b/tests/unittests/support/fifo_async_task_scheduler_test.cpp @@ -20,7 +20,7 @@ * */ -#include "srsran/support/async/async_task_loop.h" +#include "srsran/support/async/fifo_async_task_scheduler.h" #include "srsran/support/test_utils.h" #include @@ -28,8 +28,8 @@ using namespace srsran; void test_async_loop_empty_tasks() { - async_task_sequencer loop{128}; - size_t nof_tasks = 10000; + fifo_async_task_scheduler loop{128}; + size_t nof_tasks = 10000; auto tp = std::chrono::high_resolution_clock::now(); diff --git a/tests/unittests/support/task_execution_manager_test.cpp b/tests/unittests/support/task_execution_manager_test.cpp index 87e7a14104..e46e3ba12d 100644 --- a/tests/unittests/support/task_execution_manager_test.cpp +++ b/tests/unittests/support/task_execution_manager_test.cpp @@ -77,8 +77,8 @@ TEST_F(task_execution_manager_test, worker_with_queues_of_different_priorities) "WORKER", {task_queue{concurrent_queue_policy::lockfree_spsc, 8}, task_queue{concurrent_queue_policy::locking_mpsc, 8}}, std::chrono::microseconds{10}, - {priority_multiqueue_worker::executor{"EXEC1", task_priority::max, true, false}, - priority_multiqueue_worker::executor{"EXEC2", task_priority::min, true, false}}}; + {priority_multiqueue_worker::executor{"EXEC1", enqueue_priority::max, true, false}, + priority_multiqueue_worker::executor{"EXEC2", enqueue_priority::min, true, false}}}; task_execution_manager mng; ASSERT_TRUE(mng.add_execution_context(create_execution_context(cfg))); diff --git a/tests/unittests/support/task_worker_test.cpp b/tests/unittests/support/task_worker_test.cpp index bb11cbe983..03de1ff631 100644 --- a/tests/unittests/support/task_worker_test.cpp +++ b/tests/unittests/support/task_worker_test.cpp @@ -20,6 +20,7 @@ * */ +#include "srsran/support/executors/priority_task_worker.h" #include "srsran/support/executors/task_worker.h" #include "srsran/support/executors/task_worker_pool.h" #include @@ -27,6 +28,13 @@ using namespace srsran; +// Disable GCC 5's -Wsuggest-override warnings in gtest. +#ifdef __clang__ +#pragma GCC diagnostic ignored "-Wall" +#else // __clang__ +#pragma GCC diagnostic ignored "-Wsuggest-override" +#endif // __clang__ + TEST(task_worker, correct_initialization) { task_worker worker{"WORKER", 1024}; @@ -51,26 +59,80 @@ TEST(task_worker, single_pushed_task_is_run) ASSERT_EQ(count, 1); } -TEST(task_worker_pool, correct_initialization) +template +class task_worker_pool_test : public ::testing::Test { - task_worker_pool pool{4, 128, "POOL"}; - ASSERT_EQ(pool.nof_workers(), 4); - ASSERT_EQ(pool.nof_pending_tasks(), 0); -} +protected: + using pool_type = TaskWorkerPool; + + template >::value, int> = 0> + task_worker_pool_test() : pool{4, 128, "POOL", std::chrono::microseconds{100}} + { + } + template >::value, int> = 0> + task_worker_pool_test() : pool{4, 128, "POOL"} + { + } -TEST(task_worker_pool, worker_pool_runs_single_task) + pool_type pool; +}; +using worker_pool_types = ::testing::Types, task_worker_pool>; +TYPED_TEST_SUITE(task_worker_pool_test, worker_pool_types); + +TYPED_TEST(task_worker_pool_test, correct_initialization) { - task_worker_pool pool{4, 128, "POOL"}; + ASSERT_EQ(this->pool.nof_workers(), 4); + ASSERT_EQ(this->pool.nof_pending_tasks(), 0); +} +TYPED_TEST(task_worker_pool_test, worker_pool_runs_single_task) +{ std::promise p; std::future f = p.get_future(); - pool.push_task([&p]() { + ASSERT_TRUE(this->pool.push_task([&p]() { p.set_value(); fmt::print("Finished in {}\n", this_thread_name()); - }); + })); f.get(); } +TYPED_TEST(task_worker_pool_test, worker_pool_runs_tasks_in_all_workers) +{ + std::mutex mut; + std::condition_variable cvar; + unsigned count = 0; + std::vector> worker_signal(this->pool.nof_workers()); + std::vector> worker_barrier; + for (std::promise& sig : worker_signal) { + worker_barrier.push_back(sig.get_future()); + } + + for (unsigned j = 0; j != this->pool.nof_workers(); ++j) { + ASSERT_TRUE(this->pool.push_task([&mut, &cvar, &count, &worker_barrier, j]() { + { + std::lock_guard lock(mut); + count++; + cvar.notify_one(); + } + // synchronization point. + worker_barrier[j].wait(); + })); + } + + { + // Wait for all workers of the pool to reach synchronization point. + std::unique_lock lock(mut); + cvar.wait(lock, [&count, this]() { return count == this->pool.nof_workers(); }); + } + + // Unblock all workers. + for (auto& p : worker_signal) { + p.set_value(); + } + + this->pool.stop(); +} + TEST(spsc_task_worker_test, correct_initialization) { general_task_worker worker{ @@ -90,3 +152,25 @@ TEST(spsc_task_worker_test, single_pushed_task_is_run) worker.wait_pending_tasks(); ASSERT_EQ(count, 1); } + +TEST(priority_task_worker_test, priorities_respected_on_queue) +{ + priority_task_worker worker{ + "WORKER", {16, 16}, std::chrono::microseconds{100}}; + std::atomic result{0}; + + ASSERT_TRUE(worker.push_task([&]() mutable { + // This task should be executed last. + for (unsigned i = 0; i != 16; ++i) { + ASSERT_TRUE(worker.push_task([&, i]() mutable { ASSERT_EQ(result++, 16 + i); })); + } + // This task should be executed first. + for (unsigned i = 0; i != 16; ++i) { + ASSERT_TRUE(worker.push_task([&, i]() mutable { ASSERT_EQ(result++, i); })); + } + })); + + while (result != 32) { + std::this_thread::sleep_for(std::chrono::microseconds{100}); + } +} \ No newline at end of file